rivet-design 0.10.8 → 0.11.0
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/index.d.ts +47 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +277 -123
- package/dist/index.js.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.d.ts +28 -4
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +356 -123
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +84 -4
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +744 -145
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +8 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +8 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +7984 -1625
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +312 -154
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +2 -3
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +49 -39
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/designCritique.d.ts +167 -0
- package/dist/mcp/agent-variants/designCritique.d.ts.map +1 -0
- package/dist/mcp/agent-variants/designCritique.js +717 -0
- package/dist/mcp/agent-variants/designCritique.js.map +1 -0
- package/dist/mcp/agent-variants/diffQa.d.ts +7 -0
- package/dist/mcp/agent-variants/diffQa.d.ts.map +1 -0
- package/dist/mcp/agent-variants/diffQa.js +67 -0
- package/dist/mcp/agent-variants/diffQa.js.map +1 -0
- package/dist/mcp/agent-variants/index.d.ts +3 -3
- package/dist/mcp/agent-variants/index.d.ts.map +1 -1
- package/dist/mcp/agent-variants/index.js +2 -1
- package/dist/mcp/agent-variants/index.js.map +1 -1
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts +4 -2
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts.map +1 -1
- package/dist/mcp/agent-variants/pinterestSourceContext.js +7 -6
- package/dist/mcp/agent-variants/pinterestSourceContext.js.map +1 -1
- package/dist/mcp/agent-variants/previewQa.d.ts +6 -4
- package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -1
- package/dist/mcp/agent-variants/previewQa.js +140 -13
- package/dist/mcp/agent-variants/previewQa.js.map +1 -1
- package/dist/mcp/agent-variants/sourceContext.d.ts +20 -5
- package/dist/mcp/agent-variants/sourceContext.d.ts.map +1 -1
- package/dist/mcp/agent-variants/sourceContext.js +99 -115
- package/dist/mcp/agent-variants/sourceContext.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +7 -0
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +216 -15
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/agent-variants/variantContext.d.ts +19 -0
- package/dist/mcp/agent-variants/variantContext.d.ts.map +1 -0
- package/dist/mcp/agent-variants/variantContext.js +355 -0
- package/dist/mcp/agent-variants/variantContext.js.map +1 -0
- package/dist/mcp/auth/httpOAuthProvider.d.ts +103 -0
- package/dist/mcp/auth/httpOAuthProvider.d.ts.map +1 -0
- package/dist/mcp/auth/httpOAuthProvider.js +454 -0
- package/dist/mcp/auth/httpOAuthProvider.js.map +1 -0
- package/dist/mcp/auth/tools.d.ts +2 -0
- package/dist/mcp/auth/tools.d.ts.map +1 -1
- package/dist/mcp/auth/tools.js +12 -5
- package/dist/mcp/auth/tools.js.map +1 -1
- package/dist/mcp/httpServer.d.ts +36 -0
- package/dist/mcp/httpServer.d.ts.map +1 -0
- package/dist/mcp/httpServer.js +307 -0
- package/dist/mcp/httpServer.js.map +1 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +41 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
- package/dist/proxy-middleware/proxy-config.js +5 -2
- package/dist/proxy-middleware/proxy-config.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +6 -4
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +2 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +13 -5
- package/dist/server.js.map +1 -1
- package/dist/services/AuthService.d.ts +1 -0
- package/dist/services/AuthService.d.ts.map +1 -1
- package/dist/services/AuthService.js +11 -1
- package/dist/services/AuthService.js.map +1 -1
- package/dist/services/BrowserAgentClient.d.ts +54 -0
- package/dist/services/BrowserAgentClient.d.ts.map +1 -0
- package/dist/services/BrowserAgentClient.js +126 -0
- package/dist/services/BrowserAgentClient.js.map +1 -0
- package/dist/services/ConfigManager.d.ts +5 -0
- package/dist/services/ConfigManager.d.ts.map +1 -1
- package/dist/services/ConfigManager.js +25 -3
- package/dist/services/ConfigManager.js.map +1 -1
- package/dist/services/DevServerRuntimeService.d.ts +119 -0
- package/dist/services/DevServerRuntimeService.d.ts.map +1 -0
- package/dist/services/DevServerRuntimeService.js +657 -0
- package/dist/services/DevServerRuntimeService.js.map +1 -0
- package/dist/services/GatewayClient.d.ts +25 -0
- package/dist/services/GatewayClient.d.ts.map +1 -1
- package/dist/services/GatewayClient.js +70 -11
- package/dist/services/GatewayClient.js.map +1 -1
- package/dist/services/InlineVariantGenerationService.d.ts +2 -0
- package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
- package/dist/services/InlineVariantGenerationService.js +70 -3
- package/dist/services/InlineVariantGenerationService.js.map +1 -1
- package/dist/services/RequestAuthContext.d.ts +7 -1
- package/dist/services/RequestAuthContext.d.ts.map +1 -1
- package/dist/services/RequestAuthContext.js +15 -2
- package/dist/services/RequestAuthContext.js.map +1 -1
- package/dist/services/SessionBridgeService.d.ts +1 -0
- package/dist/services/SessionBridgeService.d.ts.map +1 -1
- package/dist/services/SessionBridgeService.js +16 -1
- package/dist/services/SessionBridgeService.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 +1 -0
- package/dist/services/VariantRunService.js.map +1 -1
- package/dist/services/VariantsRuntime.d.ts.map +1 -1
- package/dist/services/VariantsRuntime.js +1 -0
- package/dist/services/VariantsRuntime.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts +1 -8
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +1 -28
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +7 -0
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.d.ts +2 -3
- package/dist/utils/skills/describe-motion-protocol.d.ts.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.js +50 -35
- package/dist/utils/skills/describe-motion-protocol.js.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +21 -15
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/dist/utils/variantSessionStart.d.ts +3 -0
- package/dist/utils/variantSessionStart.d.ts.map +1 -0
- package/dist/utils/variantSessionStart.js +7 -0
- package/dist/utils/variantSessionStart.js.map +1 -0
- package/package.json +2 -1
- package/src/ui/dist/assets/{main-WqlDU4Ou.js → main-Cw6Pd8ye.js} +204 -204
- package/src/ui/dist/assets/main-DkCj7b2K.css +1 -0
- package/src/ui/dist/index.html +2 -2
- package/dist/mcp/agent-variants/designContextStore.d.ts +0 -160
- package/dist/mcp/agent-variants/designContextStore.d.ts.map +0 -1
- package/dist/mcp/agent-variants/designContextStore.js +0 -295
- package/dist/mcp/agent-variants/designContextStore.js.map +0 -1
- package/dist/mcp/agent-variants/inspirationDesignContext.d.ts +0 -440
- package/dist/mcp/agent-variants/inspirationDesignContext.d.ts.map +0 -1
- package/dist/mcp/agent-variants/inspirationDesignContext.js +0 -2467
- package/dist/mcp/agent-variants/inspirationDesignContext.js.map +0 -1
- package/src/ui/dist/assets/main-auZA25j4.css +0 -1
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentVariantsOrchestrator = void 0;
|
|
6
|
+
exports.AgentVariantsOrchestrator = exports.defaultDesignCritiqueRunner = void 0;
|
|
7
7
|
exports.buildStaticPreviewDocument = buildStaticPreviewDocument;
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const events_1 = require("events");
|
|
@@ -22,7 +22,10 @@ const viteReactTs_1 = require("../../services/templates/viteReactTs");
|
|
|
22
22
|
const StaticPreviewServer_1 = require("../../services/StaticPreviewServer");
|
|
23
23
|
const designCatalog_1 = require("../../services/templates/designCatalog");
|
|
24
24
|
const previewQa_1 = require("./previewQa");
|
|
25
|
+
const designCritique_1 = require("./designCritique");
|
|
26
|
+
const diffQa_1 = require("./diffQa");
|
|
25
27
|
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
|
|
28
|
+
const variantContext_1 = require("./variantContext");
|
|
26
29
|
const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
27
30
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
28
31
|
// Fresh worktrees run the Vite React template, whose dev server defaults to
|
|
@@ -257,8 +260,126 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
257
260
|
fs_1.default.mkdirSync(path_1.default.dirname(absDest), { recursive: true });
|
|
258
261
|
fs_1.default.copyFileSync(resolvedSource, absDest);
|
|
259
262
|
}
|
|
260
|
-
const defaultPreviewQaRunner = ({ html }) => (0, previewQa_1.runPreviewQa)({
|
|
263
|
+
const defaultPreviewQaRunner = ({ html, assetBase }) => (0, previewQa_1.runPreviewQa)({
|
|
264
|
+
html,
|
|
265
|
+
options: {
|
|
266
|
+
...(assetBase ? { localAssetBasePath: assetBase } : {}),
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
/**
|
|
270
|
+
* Production design-critique runner backed by the real GatewayClient + a
|
|
271
|
+
* Playwright screenshot. Failure-tolerant: `runDesignCritique` degrades to a
|
|
272
|
+
* non-blocking skip on any render/model error.
|
|
273
|
+
*/
|
|
274
|
+
const defaultDesignCritiqueRunner = ({ target, designContextMarkdown, designContract, }) => (0, designCritique_1.runDesignCritique)({ target, designContextMarkdown, designContract });
|
|
275
|
+
exports.defaultDesignCritiqueRunner = defaultDesignCritiqueRunner;
|
|
276
|
+
/** Merge the structured critique fields (findings/checks/caps) onto a QA
|
|
277
|
+
* verdict. Only attaches each field when the critique produced it. */
|
|
278
|
+
const attachCritiqueDetail = (qa, outcome) => ({
|
|
279
|
+
...qa,
|
|
280
|
+
...(outcome.scores
|
|
281
|
+
? {
|
|
282
|
+
dimensionScores: {
|
|
283
|
+
...outcome.scores,
|
|
284
|
+
overall: outcome.overallScore ?? outcome.scores.overall,
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
: {}),
|
|
288
|
+
...(outcome.summary ? { designSummary: outcome.summary } : {}),
|
|
289
|
+
...(outcome.findings.length > 0 ? { designFindings: outcome.findings } : {}),
|
|
290
|
+
...(outcome.checks ? { designChecks: outcome.checks } : {}),
|
|
291
|
+
...(outcome.scoreCaps.length > 0
|
|
292
|
+
? { designScoreCaps: outcome.scoreCaps }
|
|
293
|
+
: {}),
|
|
294
|
+
});
|
|
295
|
+
/** Merge passing design scores onto a base QA verdict (status unchanged). */
|
|
296
|
+
const attachDesignScores = (base, outcome) => attachCritiqueDetail(base, outcome);
|
|
297
|
+
const designScoreFragment = (outcome) => {
|
|
298
|
+
const score = outcome.overallScore ?? outcome.scores?.overall;
|
|
299
|
+
return score !== undefined ? `overall ${score.toFixed(1)}/10` : 'below bar';
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Compose the actionable critique summary handed to a re-leased variant: the
|
|
303
|
+
* model's prose plus an explicit, prioritized fix list drawn from the
|
|
304
|
+
* critical/major findings so the agent knows exactly what to change.
|
|
305
|
+
*/
|
|
306
|
+
const buildRetryCritiqueSummary = (qa) => {
|
|
307
|
+
const base = qa.designSummary ?? qa.summary;
|
|
308
|
+
const actionable = (qa.designFindings ?? []).filter((f) => f.severity === 'critical' || f.severity === 'major');
|
|
309
|
+
if (actionable.length === 0)
|
|
310
|
+
return base;
|
|
311
|
+
const fixes = actionable
|
|
312
|
+
.map((f) => `- [${f.severity} ${f.category}] ${f.evidence} → ${f.fix}`)
|
|
313
|
+
.join('\n');
|
|
314
|
+
return `${base}\n\nMust fix before re-reporting:\n${fixes}`;
|
|
315
|
+
};
|
|
316
|
+
/** One-line, actionable summary of the worst findings for chip/prompt copy. */
|
|
317
|
+
const findingsFragment = (outcome) => {
|
|
318
|
+
const ranked = outcome.findings.filter((f) => f.severity === 'critical' || f.severity === 'major');
|
|
319
|
+
if (ranked.length === 0)
|
|
320
|
+
return '';
|
|
321
|
+
const first = ranked[0];
|
|
322
|
+
const more = ranked.length > 1 ? ` (+${ranked.length - 1} more)` : '';
|
|
323
|
+
return `${first.severity} ${first.category}: ${first.evidence}${more}`;
|
|
324
|
+
};
|
|
325
|
+
/** Build a failed QA verdict for a below-bar variant (drives the re-lease). */
|
|
326
|
+
const buildDesignFailureQa = (base, outcome) => {
|
|
327
|
+
const findings = findingsFragment(outcome);
|
|
328
|
+
const detail = findings || designScoreFragment(outcome);
|
|
329
|
+
return attachCritiqueDetail({
|
|
330
|
+
status: 'failed',
|
|
331
|
+
issues: [
|
|
332
|
+
...base.issues,
|
|
333
|
+
{
|
|
334
|
+
kind: 'design_quality',
|
|
335
|
+
detail,
|
|
336
|
+
...(outcome.summary ? { message: outcome.summary } : {}),
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
summary: outcome.blocked
|
|
340
|
+
? `Design critical: ${detail}.`
|
|
341
|
+
: `Design quality below bar (${detail}).`,
|
|
342
|
+
...(base.checkedSource ? { checkedSource: base.checkedSource } : {}),
|
|
343
|
+
}, outcome);
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Build a degraded (but committable) QA verdict for a variant that stayed
|
|
347
|
+
* below the bar after its one retry. Status is `passed` so commit stays
|
|
348
|
+
* enabled — the low score is surfaced via `dimensionScores`/`designSummary`
|
|
349
|
+
* rather than hard-blocking the user forever. Only used for non-critical
|
|
350
|
+
* failures; an unresolved critical hard-fails instead of degrading.
|
|
351
|
+
*/
|
|
352
|
+
const buildDegradedDesignQa = (base, outcome) => attachCritiqueDetail({
|
|
353
|
+
...base,
|
|
354
|
+
status: 'passed',
|
|
355
|
+
summary: `Accepted with low design score (${designScoreFragment(outcome)}) after retry.`,
|
|
356
|
+
}, outcome);
|
|
261
357
|
const NOOP_TELEMETRY = { track: () => undefined };
|
|
358
|
+
const DESIGN_CREATION_ACTION_RE = /\b(create|build|design|make|redesign|refresh|update)\b/i;
|
|
359
|
+
const DESIGN_CREATION_SURFACE_RE = /\b(home\s?page|landing page|dashboard|screen|site|website|app|page|ui|interface|component|hero)\b/i;
|
|
360
|
+
const NON_VISUAL_CHANGE_RE = /\b(api|backend|database|event|lint|query|server|test|tracking|typecheck)\b/i;
|
|
361
|
+
const DIFF_CAPTURE_FAILURE_SUMMARY = 'Unable to verify the generated design diff; regenerate this variant so Rivet can confirm source changes.';
|
|
362
|
+
const buildDiffCaptureFailureQa = (detail, message) => ({
|
|
363
|
+
status: 'failed',
|
|
364
|
+
issues: [
|
|
365
|
+
{
|
|
366
|
+
kind: 'design_quality',
|
|
367
|
+
detail,
|
|
368
|
+
message,
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
summary: DIFF_CAPTURE_FAILURE_SUMMARY,
|
|
372
|
+
});
|
|
373
|
+
const isDesignCreationCodeGenInput = (input, prompt) => {
|
|
374
|
+
const codeGenInput = input;
|
|
375
|
+
const text = [prompt, codeGenInput?.briefLabel, codeGenInput?.briefBody]
|
|
376
|
+
.filter((value) => typeof value === 'string')
|
|
377
|
+
.join('\n');
|
|
378
|
+
if (NON_VISUAL_CHANGE_RE.test(text))
|
|
379
|
+
return false;
|
|
380
|
+
return (DESIGN_CREATION_ACTION_RE.test(text) &&
|
|
381
|
+
DESIGN_CREATION_SURFACE_RE.test(text));
|
|
382
|
+
};
|
|
262
383
|
/**
|
|
263
384
|
* Wraps SessionStore for the operations that have side effects: approve
|
|
264
385
|
* (provision worktrees), reportComplete (capture diff + auto-enqueue to
|
|
@@ -280,6 +401,7 @@ class AgentVariantsOrchestrator {
|
|
|
280
401
|
installDependencies;
|
|
281
402
|
materializeProject;
|
|
282
403
|
previewQaRunner;
|
|
404
|
+
designCritiqueRunner;
|
|
283
405
|
switchPreviewPort;
|
|
284
406
|
setCommittedDevServerHealth;
|
|
285
407
|
variantHistory;
|
|
@@ -323,6 +445,7 @@ class AgentVariantsOrchestrator {
|
|
|
323
445
|
this.materializeProject =
|
|
324
446
|
deps.materializeProject ?? defaultMaterializeProject;
|
|
325
447
|
this.previewQaRunner = deps.previewQaRunner ?? defaultPreviewQaRunner;
|
|
448
|
+
this.designCritiqueRunner = deps.designCritiqueRunner;
|
|
326
449
|
this.switchPreviewPort = deps.switchPreviewPort;
|
|
327
450
|
this.setCommittedDevServerHealth = deps.setCommittedDevServerHealth;
|
|
328
451
|
this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
|
|
@@ -450,6 +573,10 @@ class AgentVariantsOrchestrator {
|
|
|
450
573
|
}
|
|
451
574
|
reportBriefs(args) {
|
|
452
575
|
const result = this.store.reportBriefs(args);
|
|
576
|
+
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
577
|
+
if (projectContext.variantContext) {
|
|
578
|
+
(0, variantContext_1.writeVariantContextBundle)(projectContext.variantContext);
|
|
579
|
+
}
|
|
453
580
|
const evidenceBackedCount = result.briefs.filter((brief) => /\b(source|evidence|preserve|borrow)\b/i.test(brief.body)).length;
|
|
454
581
|
const shortBriefCount = result.briefs.filter((brief) => brief.body.length <= 200).length;
|
|
455
582
|
this.telemetry.trackAgentVariantsBriefQuality?.({
|
|
@@ -463,13 +590,25 @@ class AgentVariantsOrchestrator {
|
|
|
463
590
|
}
|
|
464
591
|
reportSourcePlan(args) {
|
|
465
592
|
const result = this.store.reportSourcePlan(args);
|
|
466
|
-
this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
593
|
+
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
594
|
+
if (projectContext.variantContext) {
|
|
595
|
+
(0, variantContext_1.writeVariantContextBundle)(projectContext.variantContext);
|
|
596
|
+
}
|
|
597
|
+
// Source-context quality is now measured off the raw context bundle plus
|
|
598
|
+
// the action metadata the agent reported, not the legacy summary fields.
|
|
599
|
+
const bundle = projectContext.kind === 'fresh'
|
|
600
|
+
? projectContext.variantContext
|
|
601
|
+
: undefined;
|
|
602
|
+
if (args.sourcePlan.metadata.length > 0 || bundle) {
|
|
603
|
+
this.telemetry.trackAgentVariantsSourceContextQuality?.({
|
|
604
|
+
sessionId: args.sessionId,
|
|
605
|
+
sourceUrlCount: bundle?.userContext.filter((source) => source.transport === 'url')
|
|
606
|
+
.length ?? 0,
|
|
607
|
+
artifactCount: args.sourcePlan.metadata.length,
|
|
608
|
+
hasScreenshotReferences: false,
|
|
609
|
+
preserveBrand: false,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
473
612
|
this.emitChange();
|
|
474
613
|
return result;
|
|
475
614
|
}
|
|
@@ -586,6 +725,9 @@ class AgentVariantsOrchestrator {
|
|
|
586
725
|
getStage(sessionId) {
|
|
587
726
|
return this.store.getStage(sessionId);
|
|
588
727
|
}
|
|
728
|
+
getWorkItemInput(sessionId, workItemId) {
|
|
729
|
+
return this.store.getWorkItemInput(sessionId, workItemId);
|
|
730
|
+
}
|
|
589
731
|
getBriefs(sessionId) {
|
|
590
732
|
return this.store.getBriefs(sessionId);
|
|
591
733
|
}
|
|
@@ -877,18 +1019,14 @@ class AgentVariantsOrchestrator {
|
|
|
877
1019
|
const projectContext = args.projectContext ?? {
|
|
878
1020
|
kind: 'existing',
|
|
879
1021
|
};
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1022
|
+
// Fresh source-grounded sessions (a captured context bundle on a fresh
|
|
1023
|
+
// project) must run through `propose` so the source-research/source_plan
|
|
1024
|
+
// flow gates generation; start_variants routes them there before reaching
|
|
1025
|
+
// here, so this guard is an internal invariant. Existing-project sessions
|
|
1026
|
+
// keep the single-call path even with a bundle — they have no source_plan
|
|
1027
|
+
// stage, and the bundle's raw context is threaded straight into code_gen.
|
|
1028
|
+
const isSourceGrounded = projectContext.kind === 'fresh' && Boolean(projectContext.variantContext);
|
|
887
1029
|
if (isSourceGrounded) {
|
|
888
|
-
// startUnified is the single-call path; source-grounded sessions must
|
|
889
|
-
// run through `propose` so the source-research flow gates generation.
|
|
890
|
-
// start_variants routes them there before reaching here — this guard is
|
|
891
|
-
// an internal invariant, not a user-facing dead end.
|
|
892
1030
|
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'Source-grounded sessions must run through the source-research flow (propose), not the single-call startUnified path.');
|
|
893
1031
|
}
|
|
894
1032
|
const proposeResult = this.propose({
|
|
@@ -897,6 +1035,7 @@ class AgentVariantsOrchestrator {
|
|
|
897
1035
|
target: args.target,
|
|
898
1036
|
projectContext,
|
|
899
1037
|
runLabel: args.runLabel,
|
|
1038
|
+
...(args.sessionId ? { sessionId: args.sessionId } : {}),
|
|
900
1039
|
});
|
|
901
1040
|
if (proposeResult.stage !== 'awaiting_briefs' ||
|
|
902
1041
|
!proposeResult.briefWorkItem) {
|
|
@@ -1005,10 +1144,15 @@ class AgentVariantsOrchestrator {
|
|
|
1005
1144
|
// verdict converts the report to `failed` with code `VARIANT_QA_FAILED`
|
|
1006
1145
|
// so the variant never reaches `ready` and `getVariants` can disable
|
|
1007
1146
|
// its commit action.
|
|
1008
|
-
const
|
|
1147
|
+
const qaEval = workItemKind === 'refine_variant'
|
|
1009
1148
|
? null
|
|
1010
1149
|
: await this.evaluateQaForReport(args);
|
|
1011
|
-
|
|
1150
|
+
// A `retry` verdict re-leases the variant for one aesthetic regeneration
|
|
1151
|
+
// instead of recording success — short-circuit the normal record path.
|
|
1152
|
+
if (qaEval?.decision === 'retry') {
|
|
1153
|
+
return this.handleDesignCritiqueRetry(args, qaEval.qa);
|
|
1154
|
+
}
|
|
1155
|
+
let effectiveArgs = qaEval?.decision === 'fail_terminal' ? qaEval.overrideArgs : args;
|
|
1012
1156
|
// For a successful refine_variant, swap the refined static preview into
|
|
1013
1157
|
// place and persist history BEFORE recording success in the store. This
|
|
1014
1158
|
// keeps the user-visible `refinement.status: succeeded` (and the snapshot
|
|
@@ -1928,7 +2072,6 @@ class AgentVariantsOrchestrator {
|
|
|
1928
2072
|
return input.designContextEntry;
|
|
1929
2073
|
})
|
|
1930
2074
|
: undefined;
|
|
1931
|
-
const sourceContext = projectContext.sourceContext;
|
|
1932
2075
|
const createFresh = this.worktrees.createFreshWorktrees;
|
|
1933
2076
|
if (!createFresh) {
|
|
1934
2077
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement createFreshWorktrees');
|
|
@@ -1950,7 +2093,7 @@ class AgentVariantsOrchestrator {
|
|
|
1950
2093
|
// Use workspaceRoot directly so staging lives at
|
|
1951
2094
|
// `<workspaceRoot>/.rivet-variants/` as originally intended.
|
|
1952
2095
|
const destinationParent = projectContext.workspaceRoot;
|
|
1953
|
-
const paths = await createFresh.call(this.worktrees, sessionId, codeGenIds.length, viteReactTs_1.VITE_REACT_TS_TEMPLATE, designContext,
|
|
2096
|
+
const paths = await createFresh.call(this.worktrees, sessionId, codeGenIds.length, viteReactTs_1.VITE_REACT_TS_TEMPLATE, designContext, destinationParent);
|
|
1954
2097
|
resources.scaffoldBaseWorkItemId = scaffoldId;
|
|
1955
2098
|
resources.freshDestinationParent = destinationParent;
|
|
1956
2099
|
// Each code_gen item maps 1:1 to a fresh worktree. The scaffold_base
|
|
@@ -2088,42 +2231,196 @@ class AgentVariantsOrchestrator {
|
|
|
2088
2231
|
return null;
|
|
2089
2232
|
if (!this.store.hasSession(args.sessionId))
|
|
2090
2233
|
return null;
|
|
2091
|
-
|
|
2092
|
-
|
|
2234
|
+
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
2235
|
+
const kind = this.store.getWorkItemKind(args.sessionId, args.workItemId);
|
|
2236
|
+
// Existing-project code_gen has no preview document for browser QA. It
|
|
2237
|
+
// still gets a cheap task-fit sanity check for design/creation requests so
|
|
2238
|
+
// deletion-only "successes" cannot pass as generated UI.
|
|
2239
|
+
if (projectContext.kind !== 'fresh') {
|
|
2240
|
+
if (kind === 'code_gen') {
|
|
2241
|
+
return this.evaluateExistingCodeGenQa(args);
|
|
2242
|
+
}
|
|
2093
2243
|
return null;
|
|
2094
2244
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2245
|
+
// Resolve the objective base QA (static_preview only) and the design
|
|
2246
|
+
// critique render target for this deliverable kind.
|
|
2247
|
+
let baseQa = null;
|
|
2248
|
+
let critiqueTarget = null;
|
|
2249
|
+
let checkedSource;
|
|
2250
|
+
if (kind === 'static_preview') {
|
|
2251
|
+
const parsed = parseStaticPreviewOutput(normalizeOutput(args.output));
|
|
2252
|
+
if (!parsed)
|
|
2253
|
+
return null;
|
|
2254
|
+
const assetBase = resolveStaticPreviewAssetBase({
|
|
2255
|
+
assetBase: parsed.assetBase,
|
|
2256
|
+
projectContext,
|
|
2257
|
+
});
|
|
2258
|
+
baseQa = await this.runPreviewQaSafely(args, parsed.html, assetBase);
|
|
2259
|
+
critiqueTarget = {
|
|
2260
|
+
kind: 'html',
|
|
2261
|
+
html: buildStaticPreviewDocument({ html: parsed.html }),
|
|
2262
|
+
...(assetBase ? { assetBase } : {}),
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
else if (kind === 'code_gen') {
|
|
2266
|
+
// Fresh vite_app variants run agent-authored dev-server code. Do not
|
|
2267
|
+
// navigate or request that server from the critique sandbox.
|
|
2097
2268
|
return null;
|
|
2098
|
-
|
|
2099
|
-
|
|
2269
|
+
}
|
|
2270
|
+
else {
|
|
2100
2271
|
return null;
|
|
2101
|
-
|
|
2272
|
+
}
|
|
2273
|
+
// Objective base QA failure is terminal exactly as before — never run the
|
|
2274
|
+
// aesthetic critique on a variant that's already broken.
|
|
2275
|
+
if (baseQa && baseQa.status === 'failed') {
|
|
2276
|
+
this.memoizeQa(args.sessionId, args.workItemId, baseQa);
|
|
2277
|
+
return {
|
|
2278
|
+
decision: 'fail_terminal',
|
|
2279
|
+
qa: baseQa,
|
|
2280
|
+
overrideArgs: {
|
|
2281
|
+
...args,
|
|
2282
|
+
status: 'failed',
|
|
2283
|
+
error: { code: 'VARIANT_QA_FAILED', message: baseQa.summary },
|
|
2284
|
+
},
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
// Synthesize a passing base verdict for vite_app (no objective layer).
|
|
2288
|
+
const passedBase = baseQa ?? {
|
|
2289
|
+
status: 'passed',
|
|
2290
|
+
issues: [],
|
|
2291
|
+
summary: 'Preview QA passed — dev server reachable.',
|
|
2292
|
+
checkedSource,
|
|
2293
|
+
};
|
|
2294
|
+
// No design critique wired → behave exactly like objective-only QA.
|
|
2295
|
+
if (!this.designCritiqueRunner) {
|
|
2296
|
+
this.memoizeQa(args.sessionId, args.workItemId, passedBase);
|
|
2297
|
+
return { decision: 'pass', qa: passedBase };
|
|
2298
|
+
}
|
|
2299
|
+
const outcome = await this.runDesignCritiqueSafely({
|
|
2300
|
+
sessionId: args.sessionId,
|
|
2301
|
+
workItemId: args.workItemId,
|
|
2302
|
+
target: critiqueTarget,
|
|
2303
|
+
});
|
|
2304
|
+
// Critique skipped (render/model unavailable) → non-blocking, keep base.
|
|
2305
|
+
if (!outcome.ran) {
|
|
2306
|
+
this.memoizeQa(args.sessionId, args.workItemId, passedBase);
|
|
2307
|
+
return { decision: 'pass', qa: passedBase };
|
|
2308
|
+
}
|
|
2309
|
+
if (outcome.passed) {
|
|
2310
|
+
const qa = attachDesignScores(passedBase, outcome);
|
|
2311
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2312
|
+
return { decision: 'pass', qa };
|
|
2313
|
+
}
|
|
2314
|
+
// Below the bar. First failure → re-lease for one regeneration.
|
|
2315
|
+
const resources = this.ensureResources(args.sessionId);
|
|
2316
|
+
if (!resources.designCritiqueRetried.has(args.workItemId)) {
|
|
2317
|
+
const qa = buildDesignFailureQa(passedBase, outcome);
|
|
2318
|
+
return { decision: 'retry', qa };
|
|
2319
|
+
}
|
|
2320
|
+
// Retry already spent. A `critical` finding is a hard gate: an unusable
|
|
2321
|
+
// variant must fail terminally (commit disabled) rather than degrade into
|
|
2322
|
+
// a committable accept — a high vibe/brand score can never carry it past a
|
|
2323
|
+
// usability-breaking defect. Non-critical (score- or major-driven) failures
|
|
2324
|
+
// degrade to committable so a single fixable flaw doesn't reject a
|
|
2325
|
+
// promising 0→1 direction forever.
|
|
2326
|
+
if (outcome.blocked) {
|
|
2327
|
+
const qa = buildDesignFailureQa(passedBase, outcome);
|
|
2328
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2329
|
+
return {
|
|
2330
|
+
decision: 'fail_terminal',
|
|
2331
|
+
qa,
|
|
2332
|
+
overrideArgs: {
|
|
2333
|
+
...args,
|
|
2334
|
+
status: 'failed',
|
|
2335
|
+
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2336
|
+
},
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
const qa = buildDegradedDesignQa(passedBase, outcome);
|
|
2340
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2341
|
+
return { decision: 'degrade', qa };
|
|
2342
|
+
}
|
|
2343
|
+
async evaluateExistingCodeGenQa(args) {
|
|
2344
|
+
const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
2345
|
+
if (!isDesignCreationCodeGenInput(input, this.store.getPrompt(args.sessionId))) {
|
|
2346
|
+
return null;
|
|
2347
|
+
}
|
|
2348
|
+
const resources = this.resources.get(args.sessionId);
|
|
2349
|
+
const record = resources?.worktrees.get(args.workItemId);
|
|
2350
|
+
if (!record) {
|
|
2351
|
+
const qa = buildDiffCaptureFailureQa('diff:worktree-record-missing', 'The succeeded report could not be checked because its worktree record is missing.');
|
|
2352
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2353
|
+
return {
|
|
2354
|
+
decision: 'fail_terminal',
|
|
2355
|
+
qa,
|
|
2356
|
+
overrideArgs: {
|
|
2357
|
+
...args,
|
|
2358
|
+
status: 'failed',
|
|
2359
|
+
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2360
|
+
},
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
let diff;
|
|
2102
2364
|
try {
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
workItemId: args.workItemId,
|
|
2106
|
-
html: parsed.html,
|
|
2107
|
-
});
|
|
2365
|
+
diff = await this.worktrees.getDiff(record.worktreePath);
|
|
2366
|
+
record.diff = diff;
|
|
2108
2367
|
}
|
|
2109
2368
|
catch (err) {
|
|
2110
|
-
|
|
2111
|
-
qa =
|
|
2369
|
+
log.warn(`getDiff failed for ${record.worktreePath}`, err);
|
|
2370
|
+
const qa = buildDiffCaptureFailureQa('diff:unavailable', 'The succeeded report could not be checked because diff capture failed.');
|
|
2371
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2372
|
+
return {
|
|
2373
|
+
decision: 'fail_terminal',
|
|
2374
|
+
qa,
|
|
2375
|
+
overrideArgs: {
|
|
2376
|
+
...args,
|
|
2377
|
+
status: 'failed',
|
|
2378
|
+
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2379
|
+
},
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
const addedContentLines = (0, diffQa_1.countAddedContentDiffLines)(diff);
|
|
2383
|
+
const hasVisualImplementation = (0, diffQa_1.hasVisualImplementationDiff)(diff);
|
|
2384
|
+
if ((0, diffQa_1.hasMarkdownInlineStyleDiff)(diff)) {
|
|
2385
|
+
const qa = {
|
|
2112
2386
|
status: 'failed',
|
|
2113
2387
|
issues: [
|
|
2114
2388
|
{
|
|
2115
|
-
kind: '
|
|
2116
|
-
detail: '
|
|
2117
|
-
message,
|
|
2389
|
+
kind: 'design_quality',
|
|
2390
|
+
detail: 'diff:mdx-inline-style',
|
|
2391
|
+
message: 'The succeeded report added an inline <style> block inside Markdown/MDX, which can cause Next/Nextra hydration mismatches.',
|
|
2118
2392
|
},
|
|
2119
2393
|
],
|
|
2120
|
-
summary:
|
|
2394
|
+
summary: 'Inline <style> blocks in Markdown/MDX can break hydration; move styling into a stylesheet or component file.',
|
|
2395
|
+
};
|
|
2396
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2397
|
+
return {
|
|
2398
|
+
decision: 'fail_terminal',
|
|
2399
|
+
qa,
|
|
2400
|
+
overrideArgs: {
|
|
2401
|
+
...args,
|
|
2402
|
+
status: 'failed',
|
|
2403
|
+
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2404
|
+
},
|
|
2121
2405
|
};
|
|
2122
2406
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
if (
|
|
2407
|
+
if (hasVisualImplementation)
|
|
2408
|
+
return null;
|
|
2409
|
+
if (addedContentLines > 0) {
|
|
2410
|
+
const qa = {
|
|
2411
|
+
status: 'failed',
|
|
2412
|
+
issues: [
|
|
2413
|
+
{
|
|
2414
|
+
kind: 'design_quality',
|
|
2415
|
+
detail: 'diff:no-visual-implementation',
|
|
2416
|
+
message: 'The succeeded report added content but did not add styling, components, or structured markup for the requested design.',
|
|
2417
|
+
},
|
|
2418
|
+
],
|
|
2419
|
+
summary: 'No visual implementation was added for this design variant; regenerate it with real layout, styling, or component changes.',
|
|
2420
|
+
};
|
|
2421
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2126
2422
|
return {
|
|
2423
|
+
decision: 'fail_terminal',
|
|
2127
2424
|
qa,
|
|
2128
2425
|
overrideArgs: {
|
|
2129
2426
|
...args,
|
|
@@ -2132,7 +2429,230 @@ class AgentVariantsOrchestrator {
|
|
|
2132
2429
|
},
|
|
2133
2430
|
};
|
|
2134
2431
|
}
|
|
2135
|
-
|
|
2432
|
+
const qa = {
|
|
2433
|
+
status: 'failed',
|
|
2434
|
+
issues: [
|
|
2435
|
+
{
|
|
2436
|
+
kind: 'design_quality',
|
|
2437
|
+
detail: 'diff:no-added-content',
|
|
2438
|
+
message: 'The succeeded report did not add any source content for the requested design.',
|
|
2439
|
+
},
|
|
2440
|
+
],
|
|
2441
|
+
summary: 'No generated content was added for this design variant; regenerate it with real homepage UI changes.',
|
|
2442
|
+
};
|
|
2443
|
+
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2444
|
+
return {
|
|
2445
|
+
decision: 'fail_terminal',
|
|
2446
|
+
qa,
|
|
2447
|
+
overrideArgs: {
|
|
2448
|
+
...args,
|
|
2449
|
+
status: 'failed',
|
|
2450
|
+
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2451
|
+
},
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
/** Run the injected preview QA runner, converting a thrown runner into a
|
|
2455
|
+
* failed `VariantQaResult` (mirrors the prior inline behavior). */
|
|
2456
|
+
async runPreviewQaSafely(args, html, assetBase) {
|
|
2457
|
+
try {
|
|
2458
|
+
return await this.previewQaRunner({
|
|
2459
|
+
sessionId: args.sessionId,
|
|
2460
|
+
workItemId: args.workItemId,
|
|
2461
|
+
html,
|
|
2462
|
+
...(assetBase ? { assetBase } : {}),
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
catch (err) {
|
|
2466
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2467
|
+
return {
|
|
2468
|
+
status: 'failed',
|
|
2469
|
+
issues: [
|
|
2470
|
+
{ kind: 'preview_unavailable', detail: 'qa_runner_error', message },
|
|
2471
|
+
],
|
|
2472
|
+
summary: `Preview QA runner threw: ${message}`,
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
/** Run the injected design critique runner; a thrown runner degrades to a
|
|
2477
|
+
* non-blocking skip so the aesthetic layer can never break the gate. */
|
|
2478
|
+
async runDesignCritiqueSafely(args) {
|
|
2479
|
+
try {
|
|
2480
|
+
const designContextMarkdown = this.resolveBoundDesignMarkdown(args.sessionId, args.workItemId);
|
|
2481
|
+
const designContract = this.resolveDesignContract(args.sessionId, args.workItemId);
|
|
2482
|
+
return await this.designCritiqueRunner({
|
|
2483
|
+
sessionId: args.sessionId,
|
|
2484
|
+
workItemId: args.workItemId,
|
|
2485
|
+
target: args.target,
|
|
2486
|
+
designContextMarkdown,
|
|
2487
|
+
designContract,
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
catch (err) {
|
|
2491
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2492
|
+
log.warn(`Design critique runner threw for ${args.sessionId}/${args.workItemId}: ${message}`);
|
|
2493
|
+
return {
|
|
2494
|
+
ran: false,
|
|
2495
|
+
passed: true,
|
|
2496
|
+
blocked: false,
|
|
2497
|
+
findings: [],
|
|
2498
|
+
scoreCaps: [],
|
|
2499
|
+
skippedReason: `runner: ${message}`,
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
/** Resolve the DESIGN.md markdown bound to a variant work item, if any. */
|
|
2504
|
+
resolveBoundDesignMarkdown(sessionId, workItemId) {
|
|
2505
|
+
try {
|
|
2506
|
+
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2507
|
+
return resolveDesignArtifact(input.designContextEntry)?.markdown;
|
|
2508
|
+
}
|
|
2509
|
+
catch {
|
|
2510
|
+
return undefined;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Derive the task contract (artifact type + explicit requirements) for a
|
|
2515
|
+
* variant from the session prompt and the variant's brief body, so the
|
|
2516
|
+
* critique judges the variant against what was actually requested. Returns
|
|
2517
|
+
* undefined when no request copy is available.
|
|
2518
|
+
*/
|
|
2519
|
+
resolveDesignContract(sessionId, workItemId) {
|
|
2520
|
+
try {
|
|
2521
|
+
const prompt = this.store.getPrompt(sessionId);
|
|
2522
|
+
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2523
|
+
const contract = (0, designCritique_1.buildDesignContract)({
|
|
2524
|
+
prompt,
|
|
2525
|
+
brief: input.briefBody,
|
|
2526
|
+
});
|
|
2527
|
+
if (contract.requirements.length === 0)
|
|
2528
|
+
return undefined;
|
|
2529
|
+
return contract;
|
|
2530
|
+
}
|
|
2531
|
+
catch {
|
|
2532
|
+
return undefined;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
memoizeQa(sessionId, workItemId, qa) {
|
|
2536
|
+
this.ensureResources(sessionId).qaResults.set(workItemId, qa);
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Handle a `retry` QA verdict: consume the agent's report, re-open the
|
|
2540
|
+
* variant work item so it can be re-leased and regenerated against the
|
|
2541
|
+
* critique, and record that it has now used its single retry.
|
|
2542
|
+
*/
|
|
2543
|
+
async handleDesignCritiqueRetry(args, qa) {
|
|
2544
|
+
const resources = this.ensureResources(args.sessionId);
|
|
2545
|
+
const result = this.store.requeueForDesignCritique({
|
|
2546
|
+
sessionId: args.sessionId,
|
|
2547
|
+
workItemId: args.workItemId,
|
|
2548
|
+
leaseId: args.leaseId,
|
|
2549
|
+
attempt: args.attempt,
|
|
2550
|
+
critique: {
|
|
2551
|
+
summary: buildRetryCritiqueSummary(qa),
|
|
2552
|
+
dimensionScores: qa.dimensionScores,
|
|
2553
|
+
},
|
|
2554
|
+
});
|
|
2555
|
+
resources.designCritiqueRetried.add(args.workItemId);
|
|
2556
|
+
resources.qaResults.delete(args.workItemId);
|
|
2557
|
+
this.telemetry.track('agent_variants.variant_design_requeued', {
|
|
2558
|
+
source: 'mcp',
|
|
2559
|
+
sessionId: args.sessionId,
|
|
2560
|
+
workItemId: args.workItemId,
|
|
2561
|
+
attempt: args.attempt,
|
|
2562
|
+
overall: qa.dimensionScores?.overall ?? null,
|
|
2563
|
+
});
|
|
2564
|
+
this.emitChange();
|
|
2565
|
+
return result;
|
|
2566
|
+
}
|
|
2567
|
+
async startVariantDevServer(args) {
|
|
2568
|
+
if (args.record.port !== undefined && args.record.devServerProcess) {
|
|
2569
|
+
return args.record.port;
|
|
2570
|
+
}
|
|
2571
|
+
// Bring up a dev server in the variant's worktree so the user can cycle
|
|
2572
|
+
// through live variants in the iframe via the chip. Failures here are
|
|
2573
|
+
// logged but non-fatal — the user can still pick by reading the diff.
|
|
2574
|
+
//
|
|
2575
|
+
// Pick a sensible port near the framework's default (3000 for Next, 5173
|
|
2576
|
+
// for Vite) and inject it via PORT + the CLI flag (see buildDevServerCommand)
|
|
2577
|
+
// so the server actually binds it — that's what moves a preview off a port
|
|
2578
|
+
// the user's own dev server already holds instead of colliding on it. On a
|
|
2579
|
+
// retry we scan past the port we just tried, so a transient race or a
|
|
2580
|
+
// briefly-held port resolves onto the next free one.
|
|
2581
|
+
const startPort = await this.resolveDevServerStartPort(args.sessionId, args.isFresh);
|
|
2582
|
+
let scanFrom = startPort;
|
|
2583
|
+
const maxAttempts = 2;
|
|
2584
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2585
|
+
let port;
|
|
2586
|
+
try {
|
|
2587
|
+
port = await this.worktrees.findFreePortFrom(scanFrom);
|
|
2588
|
+
const dev = await this.resolveDevServer(args.sessionId, args.workItemId, args.record.worktreePath, port, args.isFresh);
|
|
2589
|
+
const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
|
|
2590
|
+
args.record.port = port;
|
|
2591
|
+
args.record.devServerProcess = proc;
|
|
2592
|
+
// If the dev server dies on its own (crash, OOM, Vite hard-fail),
|
|
2593
|
+
// clear the port so the iframe stops routing the chip onto a dead
|
|
2594
|
+
// socket. Without this the proxy retargets onto an unreachable port
|
|
2595
|
+
// and the chip shows "upstream_unreachable" instead of the accurate
|
|
2596
|
+
// "Preview is unavailable for this variant".
|
|
2597
|
+
proc.once('exit', (code, signal) => {
|
|
2598
|
+
if (args.record.devServerProcess !== proc)
|
|
2599
|
+
return;
|
|
2600
|
+
args.record.port = undefined;
|
|
2601
|
+
args.record.devServerProcess = undefined;
|
|
2602
|
+
log.warn(`Variant ${args.workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); preview disabled`);
|
|
2603
|
+
this.emitChange();
|
|
2604
|
+
});
|
|
2605
|
+
this.emitChange();
|
|
2606
|
+
trackFreshDevServerStarted(this.telemetry, {
|
|
2607
|
+
sessionId: args.sessionId,
|
|
2608
|
+
variantId: args.workItemId,
|
|
2609
|
+
port,
|
|
2610
|
+
});
|
|
2611
|
+
log.info(`Variant ${args.workItemId} dev server up on port ${port} (worktree ${args.record.worktreePath}; cmd: ${dev.cmd} ${dev.args.join(' ')})`);
|
|
2612
|
+
return port;
|
|
2613
|
+
}
|
|
2614
|
+
catch (err) {
|
|
2615
|
+
const portInUse = (0, devServerError_1.isDevServerStartError)(err) && err.portInUse;
|
|
2616
|
+
const reason = (0, devServerError_1.isDevServerStartError)(err) ? err.reason : 'unknown';
|
|
2617
|
+
const willRetry = portInUse && attempt < maxAttempts;
|
|
2618
|
+
// Next attempt scans past the port we just tried so we don't re-pick a
|
|
2619
|
+
// port that's persistently held (vs. a transient race that freed it).
|
|
2620
|
+
scanFrom = (port ?? scanFrom) + 1;
|
|
2621
|
+
log.warn(`Failed to start dev server for variant ${args.workItemId} on port ${port ?? 'unallocated'} ` +
|
|
2622
|
+
`(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse})` +
|
|
2623
|
+
(willRetry
|
|
2624
|
+
? '; retrying on a fresh port'
|
|
2625
|
+
: '; live preview disabled for this variant'), err);
|
|
2626
|
+
if (willRetry)
|
|
2627
|
+
continue;
|
|
2628
|
+
// Record the failure so getVariants can tell the UI this direction's
|
|
2629
|
+
// preview couldn't start (and why), then push a snapshot so the chip
|
|
2630
|
+
// updates from "loading" to the accurate per-direction message.
|
|
2631
|
+
this.resources
|
|
2632
|
+
.get(args.sessionId)
|
|
2633
|
+
?.previewFailures.set(args.workItemId, {
|
|
2634
|
+
reason,
|
|
2635
|
+
portInUse,
|
|
2636
|
+
});
|
|
2637
|
+
trackFreshDevServerFailed(this.telemetry, {
|
|
2638
|
+
sessionId: args.sessionId,
|
|
2639
|
+
variantId: args.workItemId,
|
|
2640
|
+
errorCode: 'DEV_SERVER_START_FAILED',
|
|
2641
|
+
reason,
|
|
2642
|
+
portInUse,
|
|
2643
|
+
});
|
|
2644
|
+
this.emitChange();
|
|
2645
|
+
return undefined;
|
|
2646
|
+
}
|
|
2647
|
+
finally {
|
|
2648
|
+
// Hand the reservation back whether the dev server bound the port
|
|
2649
|
+
// (now visible to the listener check, so the reservation is redundant),
|
|
2650
|
+
// it failed (port free again), or we're about to retry on the next port.
|
|
2651
|
+
if (port !== undefined)
|
|
2652
|
+
this.worktrees.releasePort?.(port);
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
return undefined;
|
|
2136
2656
|
}
|
|
2137
2657
|
async handleSucceededReport(args) {
|
|
2138
2658
|
const { sessionId, workItemId } = args;
|
|
@@ -2228,7 +2748,8 @@ class AgentVariantsOrchestrator {
|
|
|
2228
2748
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
2229
2749
|
const isFresh = projectContext.kind === 'fresh';
|
|
2230
2750
|
try {
|
|
2231
|
-
record.diff =
|
|
2751
|
+
record.diff =
|
|
2752
|
+
record.diff ?? (await this.worktrees.getDiff(record.worktreePath));
|
|
2232
2753
|
log.info(`Variant ${workItemId} diff captured (${countDiffFiles(record.diff)} files)`);
|
|
2233
2754
|
this.emitChange();
|
|
2234
2755
|
}
|
|
@@ -2278,88 +2799,12 @@ class AgentVariantsOrchestrator {
|
|
|
2278
2799
|
return;
|
|
2279
2800
|
}
|
|
2280
2801
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
// so the server actually binds it — that's what moves a preview off a port
|
|
2288
|
-
// the user's own dev server already holds instead of colliding on it. On a
|
|
2289
|
-
// retry we scan past the port we just tried, so a transient race or a
|
|
2290
|
-
// briefly-held port resolves onto the next free one.
|
|
2291
|
-
const startPort = await this.resolveDevServerStartPort(sessionId, isFresh);
|
|
2292
|
-
let scanFrom = startPort;
|
|
2293
|
-
const maxAttempts = 2;
|
|
2294
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2295
|
-
let port;
|
|
2296
|
-
try {
|
|
2297
|
-
port = await this.worktrees.findFreePortFrom(scanFrom);
|
|
2298
|
-
const dev = await this.resolveDevServer(sessionId, record.worktreePath, port, isFresh);
|
|
2299
|
-
const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
|
|
2300
|
-
record.port = port;
|
|
2301
|
-
record.devServerProcess = proc;
|
|
2302
|
-
// If the dev server dies on its own (crash, OOM, Vite hard-fail),
|
|
2303
|
-
// clear the port so the iframe stops routing the chip onto a dead
|
|
2304
|
-
// socket. Without this the proxy retargets onto an unreachable port
|
|
2305
|
-
// and the chip shows "upstream_unreachable" instead of the accurate
|
|
2306
|
-
// "Preview is unavailable for this variant".
|
|
2307
|
-
proc.once('exit', (code, signal) => {
|
|
2308
|
-
if (record.devServerProcess !== proc)
|
|
2309
|
-
return;
|
|
2310
|
-
record.port = undefined;
|
|
2311
|
-
record.devServerProcess = undefined;
|
|
2312
|
-
log.warn(`Variant ${workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); preview disabled`);
|
|
2313
|
-
this.emitChange();
|
|
2314
|
-
});
|
|
2315
|
-
this.emitChange();
|
|
2316
|
-
trackFreshDevServerStarted(this.telemetry, {
|
|
2317
|
-
sessionId,
|
|
2318
|
-
variantId: workItemId,
|
|
2319
|
-
port,
|
|
2320
|
-
});
|
|
2321
|
-
log.info(`Variant ${workItemId} dev server up on port ${port} (worktree ${record.worktreePath}; cmd: ${dev.cmd} ${dev.args.join(' ')})`);
|
|
2322
|
-
return;
|
|
2323
|
-
}
|
|
2324
|
-
catch (err) {
|
|
2325
|
-
const portInUse = (0, devServerError_1.isDevServerStartError)(err) && err.portInUse;
|
|
2326
|
-
const reason = (0, devServerError_1.isDevServerStartError)(err) ? err.reason : 'unknown';
|
|
2327
|
-
const willRetry = portInUse && attempt < maxAttempts;
|
|
2328
|
-
// Next attempt scans past the port we just tried so we don't re-pick a
|
|
2329
|
-
// port that's persistently held (vs. a transient race that freed it).
|
|
2330
|
-
scanFrom = (port ?? scanFrom) + 1;
|
|
2331
|
-
log.warn(`Failed to start dev server for variant ${workItemId} on port ${port ?? 'unallocated'} ` +
|
|
2332
|
-
`(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse})` +
|
|
2333
|
-
(willRetry
|
|
2334
|
-
? '; retrying on a fresh port'
|
|
2335
|
-
: '; live preview disabled for this variant'), err);
|
|
2336
|
-
if (willRetry)
|
|
2337
|
-
continue;
|
|
2338
|
-
// Record the failure so getVariants can tell the UI this direction's
|
|
2339
|
-
// preview couldn't start (and why), then push a snapshot so the chip
|
|
2340
|
-
// updates from "loading" to the accurate per-direction message.
|
|
2341
|
-
this.resources.get(sessionId)?.previewFailures.set(workItemId, {
|
|
2342
|
-
reason,
|
|
2343
|
-
portInUse,
|
|
2344
|
-
});
|
|
2345
|
-
trackFreshDevServerFailed(this.telemetry, {
|
|
2346
|
-
sessionId,
|
|
2347
|
-
variantId: workItemId,
|
|
2348
|
-
errorCode: 'DEV_SERVER_START_FAILED',
|
|
2349
|
-
reason,
|
|
2350
|
-
portInUse,
|
|
2351
|
-
});
|
|
2352
|
-
this.emitChange();
|
|
2353
|
-
return;
|
|
2354
|
-
}
|
|
2355
|
-
finally {
|
|
2356
|
-
// Hand the reservation back whether the dev server bound the port
|
|
2357
|
-
// (now visible to the listener check, so the reservation is redundant),
|
|
2358
|
-
// it failed (port free again), or we're about to retry on the next port.
|
|
2359
|
-
if (port !== undefined)
|
|
2360
|
-
this.worktrees.releasePort?.(port);
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2802
|
+
await this.startVariantDevServer({
|
|
2803
|
+
sessionId,
|
|
2804
|
+
workItemId,
|
|
2805
|
+
record,
|
|
2806
|
+
isFresh,
|
|
2807
|
+
});
|
|
2363
2808
|
}
|
|
2364
2809
|
async handleStaticPreviewRefinement(args) {
|
|
2365
2810
|
const resources = this.resources.get(args.sessionId);
|
|
@@ -2599,6 +3044,7 @@ class AgentVariantsOrchestrator {
|
|
|
2599
3044
|
}
|
|
2600
3045
|
else if (staticPreview) {
|
|
2601
3046
|
tmpStagingDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `rivet-variant-${args.workItemId}-`));
|
|
3047
|
+
copyStaticPreviewDirectory(staticPreview.assetBase, tmpStagingDir);
|
|
2602
3048
|
// Read the materialized index.html from the variant's per-variant dir.
|
|
2603
3049
|
let stagedHtml = '';
|
|
2604
3050
|
try {
|
|
@@ -2811,7 +3257,7 @@ class AgentVariantsOrchestrator {
|
|
|
2811
3257
|
* always use the Vite template's npm command at the worktree root; existing
|
|
2812
3258
|
* projects defer to the user's framework/packageManager config.
|
|
2813
3259
|
*/
|
|
2814
|
-
async resolveDevServer(sessionId, worktreePath, port, isFresh) {
|
|
3260
|
+
async resolveDevServer(sessionId, workItemId, worktreePath, port, isFresh) {
|
|
2815
3261
|
if (isFresh) {
|
|
2816
3262
|
return {
|
|
2817
3263
|
cwd: worktreePath,
|
|
@@ -2829,7 +3275,20 @@ class AgentVariantsOrchestrator {
|
|
|
2829
3275
|
};
|
|
2830
3276
|
}
|
|
2831
3277
|
const env = await this.resolveEnv(sessionId);
|
|
2832
|
-
const cwd = await this.
|
|
3278
|
+
const cwd = await this.resolveExistingProjectCwd(sessionId, worktreePath, env);
|
|
3279
|
+
const packageManager = detectPackageManagerForCwd(cwd, env.packageManager);
|
|
3280
|
+
await this.ensureExistingProjectDependencies({
|
|
3281
|
+
sessionId,
|
|
3282
|
+
workItemId,
|
|
3283
|
+
cwd,
|
|
3284
|
+
packageManager,
|
|
3285
|
+
});
|
|
3286
|
+
if (packageManager !== env.packageManager) {
|
|
3287
|
+
return {
|
|
3288
|
+
cwd,
|
|
3289
|
+
...buildExistingProjectDevCommand(packageManager, env.devCommand, port),
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
2833
3292
|
if (env.buildDevCommand) {
|
|
2834
3293
|
return { cwd, ...env.buildDevCommand(port) };
|
|
2835
3294
|
}
|
|
@@ -2840,6 +3299,44 @@ class AgentVariantsOrchestrator {
|
|
|
2840
3299
|
env: { PORT: String(port) },
|
|
2841
3300
|
};
|
|
2842
3301
|
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Existing-project variants usually reuse the user's installed
|
|
3304
|
+
* node_modules. Nested example projects may be standalone packages with no
|
|
3305
|
+
* install in the cloned worktree; install only when the package has no
|
|
3306
|
+
* node_modules so normal user projects are not penalized.
|
|
3307
|
+
*/
|
|
3308
|
+
async ensureExistingProjectDependencies(args) {
|
|
3309
|
+
const packageJsonPath = path_1.default.join(args.cwd, 'package.json');
|
|
3310
|
+
const nodeModulesPath = path_1.default.join(args.cwd, 'node_modules');
|
|
3311
|
+
if (!fs_1.default.existsSync(packageJsonPath) || fs_1.default.existsSync(nodeModulesPath)) {
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
await this.installDependencies(args.cwd, args.packageManager);
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* Resolve the dev-server working directory for an existing-project variant.
|
|
3318
|
+
* WorktreeManager is constructed around the editor/root project, but
|
|
3319
|
+
* `start_variants` can target a nested app (for example `examples/blog`).
|
|
3320
|
+
* In that case the worktree root is still the git root clone, while the dev
|
|
3321
|
+
* server must run from the nested project path.
|
|
3322
|
+
*/
|
|
3323
|
+
async resolveExistingProjectCwd(sessionId, worktreePath, env) {
|
|
3324
|
+
const fallback = await this.worktrees.getProjectCwdInWorktree(worktreePath);
|
|
3325
|
+
try {
|
|
3326
|
+
const projectContext = this.store.getProjectContext(sessionId);
|
|
3327
|
+
if (projectContext.kind !== 'existing' || !projectContext.projectPath) {
|
|
3328
|
+
return fallback;
|
|
3329
|
+
}
|
|
3330
|
+
const rel = path_1.default.relative(path_1.default.resolve(env.projectPath), path_1.default.resolve(projectContext.projectPath));
|
|
3331
|
+
if (!rel || rel.startsWith('..') || path_1.default.isAbsolute(rel)) {
|
|
3332
|
+
return fallback;
|
|
3333
|
+
}
|
|
3334
|
+
return path_1.default.join(worktreePath, rel);
|
|
3335
|
+
}
|
|
3336
|
+
catch {
|
|
3337
|
+
return fallback;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
2843
3340
|
/**
|
|
2844
3341
|
* After the chosen variant has been renamed into `destinationPath`, move
|
|
2845
3342
|
* each unchosen sibling worktree into a `<destinationParent>/<slug>-variants/NN-<label>/`
|
|
@@ -3266,6 +3763,7 @@ class AgentVariantsOrchestrator {
|
|
|
3266
3763
|
startedAt: Date.now(),
|
|
3267
3764
|
leasedAt: new Map(),
|
|
3268
3765
|
qaResults: new Map(),
|
|
3766
|
+
designCritiqueRetried: new Set(),
|
|
3269
3767
|
previewFailures: new Map(),
|
|
3270
3768
|
vitePreservedSiblings: false,
|
|
3271
3769
|
};
|
|
@@ -3490,8 +3988,27 @@ const enrichDesignSource = (design) => {
|
|
|
3490
3988
|
};
|
|
3491
3989
|
};
|
|
3492
3990
|
const toActiveProjectContext = (projectContext) => {
|
|
3991
|
+
// Source count + grounding are derived purely from the read-only context
|
|
3992
|
+
// bundle now that `sourceContext` intake is gone.
|
|
3993
|
+
const sourceCount = projectContext.variantContext?.userContext.filter((source) => source.transport !== 'prompt').length ?? 0;
|
|
3994
|
+
const hasSourceContextSummary = Boolean(sourceCount > 0 ||
|
|
3995
|
+
(projectContext.variantContext?.rivetMetadata.length ?? 0) > 0);
|
|
3996
|
+
const activeSourceContext = hasSourceContextSummary
|
|
3997
|
+
? {
|
|
3998
|
+
sourceCount,
|
|
3999
|
+
isSourceGrounded: Boolean(sourceCount > 0 ||
|
|
4000
|
+
projectContext.designContext?.length ||
|
|
4001
|
+
projectContext.variantContext?.rivetMetadata.length),
|
|
4002
|
+
}
|
|
4003
|
+
: undefined;
|
|
3493
4004
|
if (projectContext.kind === 'existing') {
|
|
3494
|
-
return {
|
|
4005
|
+
return {
|
|
4006
|
+
kind: 'existing',
|
|
4007
|
+
designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
|
|
4008
|
+
? { kind: 'slug', slug: entry.slug }
|
|
4009
|
+
: { kind: 'markdown', label: entry.label }),
|
|
4010
|
+
...(activeSourceContext ? { sourceContext: activeSourceContext } : {}),
|
|
4011
|
+
};
|
|
3495
4012
|
}
|
|
3496
4013
|
return {
|
|
3497
4014
|
kind: 'fresh',
|
|
@@ -3501,14 +4018,7 @@ const toActiveProjectContext = (projectContext) => {
|
|
|
3501
4018
|
designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
|
|
3502
4019
|
? { kind: 'slug', slug: entry.slug }
|
|
3503
4020
|
: { kind: 'markdown', label: entry.label }),
|
|
3504
|
-
...(
|
|
3505
|
-
? {
|
|
3506
|
-
sourceContext: {
|
|
3507
|
-
sourceCount: projectContext.sourceContext.sourceUrls?.length ?? 0,
|
|
3508
|
-
isSourceGrounded: Boolean(projectContext.sourceContext.artifact),
|
|
3509
|
-
},
|
|
3510
|
-
}
|
|
3511
|
-
: {}),
|
|
4021
|
+
...(activeSourceContext ? { sourceContext: activeSourceContext } : {}),
|
|
3512
4022
|
...(projectContext.executionPlan
|
|
3513
4023
|
? {
|
|
3514
4024
|
executionPlan: {
|
|
@@ -3546,8 +4056,6 @@ const findDesignContextArtifact = (projectContext, artifactId) => {
|
|
|
3546
4056
|
return buildResolvedDesignContextArtifacts(projectContext).find((artifact) => artifact.id === artifactId);
|
|
3547
4057
|
};
|
|
3548
4058
|
const buildResolvedDesignContextArtifacts = (projectContext) => {
|
|
3549
|
-
if (projectContext.kind !== 'fresh')
|
|
3550
|
-
return [];
|
|
3551
4059
|
const designContext = projectContext.designContext;
|
|
3552
4060
|
if (!designContext || designContext.length === 0)
|
|
3553
4061
|
return [];
|
|
@@ -4046,9 +4554,70 @@ const MATERIALIZE_EXCLUDE = new Set([
|
|
|
4046
4554
|
'.cache',
|
|
4047
4555
|
'.vite',
|
|
4048
4556
|
]);
|
|
4049
|
-
const
|
|
4557
|
+
const STATIC_PREVIEW_HISTORY_EXCLUDE = new Set([
|
|
4558
|
+
...MATERIALIZE_EXCLUDE,
|
|
4559
|
+
'.rivet',
|
|
4560
|
+
]);
|
|
4561
|
+
const STATIC_PREVIEW_HISTORY_FILE_EXTENSIONS = new Set([
|
|
4562
|
+
...ALLOWED_ASSET_EXTENSIONS,
|
|
4563
|
+
'.css',
|
|
4564
|
+
'.htm',
|
|
4565
|
+
'.html',
|
|
4566
|
+
'.js',
|
|
4567
|
+
]);
|
|
4568
|
+
function copyStaticPreviewDirectory(src, dest) {
|
|
4569
|
+
fs_1.default.mkdirSync(dest, { recursive: true });
|
|
4570
|
+
let entries;
|
|
4571
|
+
try {
|
|
4572
|
+
entries = fs_1.default.readdirSync(src, { withFileTypes: true });
|
|
4573
|
+
}
|
|
4574
|
+
catch {
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
for (const entry of entries) {
|
|
4578
|
+
if (entry.name.startsWith('.') ||
|
|
4579
|
+
STATIC_PREVIEW_HISTORY_EXCLUDE.has(entry.name)) {
|
|
4580
|
+
continue;
|
|
4581
|
+
}
|
|
4582
|
+
const from = path_1.default.join(src, entry.name);
|
|
4583
|
+
const to = path_1.default.join(dest, entry.name);
|
|
4584
|
+
if (entry.isDirectory()) {
|
|
4585
|
+
copyStaticPreviewDirectory(from, to);
|
|
4586
|
+
}
|
|
4587
|
+
else if (entry.isFile()) {
|
|
4588
|
+
const extension = path_1.default.extname(entry.name).toLowerCase();
|
|
4589
|
+
if (!STATIC_PREVIEW_HISTORY_FILE_EXTENSIONS.has(extension))
|
|
4590
|
+
continue;
|
|
4591
|
+
let stat;
|
|
4592
|
+
try {
|
|
4593
|
+
stat = fs_1.default.statSync(from);
|
|
4594
|
+
}
|
|
4595
|
+
catch {
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
4599
|
+
continue;
|
|
4600
|
+
fs_1.default.mkdirSync(path_1.default.dirname(to), { recursive: true });
|
|
4601
|
+
fs_1.default.copyFileSync(from, to);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
const buildInstallCommand = (packageManager) => {
|
|
4606
|
+
if (packageManager === 'npm') {
|
|
4607
|
+
return {
|
|
4608
|
+
cmd: 'npm',
|
|
4609
|
+
args: ['install', '--no-audit', '--no-fund', '--ignore-scripts'],
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
return {
|
|
4613
|
+
cmd: packageManager,
|
|
4614
|
+
args: ['install', '--ignore-scripts'],
|
|
4615
|
+
};
|
|
4616
|
+
};
|
|
4617
|
+
const defaultInstallDependencies = (worktreePath, packageManager = 'npm') => {
|
|
4618
|
+
const install = buildInstallCommand(packageManager);
|
|
4050
4619
|
return new Promise((resolve, reject) => {
|
|
4051
|
-
const proc = (0, child_process_1.spawn)(
|
|
4620
|
+
const proc = (0, child_process_1.spawn)(install.cmd, install.args, {
|
|
4052
4621
|
cwd: worktreePath,
|
|
4053
4622
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
4054
4623
|
});
|
|
@@ -4062,10 +4631,40 @@ const defaultInstallDependencies = (worktreePath) => {
|
|
|
4062
4631
|
resolve();
|
|
4063
4632
|
return;
|
|
4064
4633
|
}
|
|
4065
|
-
reject(new Error(
|
|
4634
|
+
reject(new Error(`${install.cmd} install in ${worktreePath} failed (code ${code}): ${stderr.slice(-512)}`));
|
|
4066
4635
|
});
|
|
4067
4636
|
});
|
|
4068
4637
|
};
|
|
4638
|
+
const detectPackageManagerForCwd = (cwd, fallback) => {
|
|
4639
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, 'yarn.lock')))
|
|
4640
|
+
return 'yarn';
|
|
4641
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, 'pnpm-lock.yaml')))
|
|
4642
|
+
return 'pnpm';
|
|
4643
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, 'package-lock.json')))
|
|
4644
|
+
return 'npm';
|
|
4645
|
+
if (fallback === 'pnpm' || fallback === 'yarn' || fallback === 'npm') {
|
|
4646
|
+
// A nested package without a lockfile should not inherit a root Yarn Berry
|
|
4647
|
+
// workspace when it is not declared as a workspace member; npm treats it as
|
|
4648
|
+
// an independent package and is the safest default for old examples.
|
|
4649
|
+
return fs_1.default.existsSync(path_1.default.join(cwd, 'package.json')) ? 'npm' : fallback;
|
|
4650
|
+
}
|
|
4651
|
+
return 'npm';
|
|
4652
|
+
};
|
|
4653
|
+
const buildExistingProjectDevCommand = (packageManager, devCommand, port) => {
|
|
4654
|
+
const env = { PORT: String(port) };
|
|
4655
|
+
if (packageManager === 'npm') {
|
|
4656
|
+
return {
|
|
4657
|
+
cmd: 'npm',
|
|
4658
|
+
args: ['run', devCommand, '--', '--port', String(port)],
|
|
4659
|
+
env,
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
return {
|
|
4663
|
+
cmd: packageManager,
|
|
4664
|
+
args: [devCommand, '--port', String(port)],
|
|
4665
|
+
env,
|
|
4666
|
+
};
|
|
4667
|
+
};
|
|
4069
4668
|
/**
|
|
4070
4669
|
* True when `a` and `b` (or the closest existing ancestor of each) live on
|
|
4071
4670
|
* the same filesystem volume. Used by commitVariant to decide between an
|