rivet-design 0.11.11 → 0.11.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.
Files changed (105) hide show
  1. package/dist/install/harnesses.d.ts.map +1 -1
  2. package/dist/install/harnesses.js +122 -4
  3. package/dist/install/harnesses.js.map +1 -1
  4. package/dist/mcp/agent-variants/SessionStore.d.ts +150 -10
  5. package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
  6. package/dist/mcp/agent-variants/SessionStore.js +468 -23
  7. package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
  8. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +143 -51
  9. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  10. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +465 -299
  11. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  12. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +5 -4
  13. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
  14. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +24 -5
  15. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
  16. package/dist/mcp/agent-variants/contracts.d.ts +102 -89
  17. package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
  18. package/dist/mcp/agent-variants/contracts.js +29 -14
  19. package/dist/mcp/agent-variants/contracts.js.map +1 -1
  20. package/dist/mcp/agent-variants/index.d.ts +3 -3
  21. package/dist/mcp/agent-variants/index.d.ts.map +1 -1
  22. package/dist/mcp/agent-variants/index.js +1 -2
  23. package/dist/mcp/agent-variants/index.js.map +1 -1
  24. package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -1
  25. package/dist/mcp/agent-variants/previewQa.js +0 -6
  26. package/dist/mcp/agent-variants/previewQa.js.map +1 -1
  27. package/dist/mcp/agent-variants/tools.d.ts +12 -4
  28. package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
  29. package/dist/mcp/agent-variants/tools.js +69 -13
  30. package/dist/mcp/agent-variants/tools.js.map +1 -1
  31. package/dist/mcp/changeBatchClassification.d.ts +30 -0
  32. package/dist/mcp/changeBatchClassification.d.ts.map +1 -1
  33. package/dist/mcp/changeBatchClassification.js +76 -0
  34. package/dist/mcp/changeBatchClassification.js.map +1 -1
  35. package/dist/mcp/instructions.d.ts.map +1 -1
  36. package/dist/mcp/instructions.js +7 -1
  37. package/dist/mcp/instructions.js.map +1 -1
  38. package/dist/mcp/server.d.ts +51 -3
  39. package/dist/mcp/server.d.ts.map +1 -1
  40. package/dist/mcp/server.js +404 -156
  41. package/dist/mcp/server.js.map +1 -1
  42. package/dist/mcp/watchLoopResumePrompt.d.ts +16 -0
  43. package/dist/mcp/watchLoopResumePrompt.d.ts.map +1 -0
  44. package/dist/mcp/watchLoopResumePrompt.js +21 -0
  45. package/dist/mcp/watchLoopResumePrompt.js.map +1 -0
  46. package/dist/routes/agentVariants.d.ts +10 -1
  47. package/dist/routes/agentVariants.d.ts.map +1 -1
  48. package/dist/routes/agentVariants.js +214 -3
  49. package/dist/routes/agentVariants.js.map +1 -1
  50. package/dist/routes/mcp.d.ts.map +1 -1
  51. package/dist/routes/mcp.js +106 -47
  52. package/dist/routes/mcp.js.map +1 -1
  53. package/dist/services/BridgeBatchPipeline.d.ts +99 -0
  54. package/dist/services/BridgeBatchPipeline.d.ts.map +1 -0
  55. package/dist/services/BridgeBatchPipeline.js +278 -0
  56. package/dist/services/BridgeBatchPipeline.js.map +1 -0
  57. package/dist/services/BridgeQueueStore.d.ts +28 -0
  58. package/dist/services/BridgeQueueStore.d.ts.map +1 -0
  59. package/dist/services/BridgeQueueStore.js +88 -0
  60. package/dist/services/BridgeQueueStore.js.map +1 -0
  61. package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
  62. package/dist/services/InlineVariantGenerationService.js +15 -3
  63. package/dist/services/InlineVariantGenerationService.js.map +1 -1
  64. package/dist/services/SessionBridgeService.d.ts +127 -31
  65. package/dist/services/SessionBridgeService.d.ts.map +1 -1
  66. package/dist/services/SessionBridgeService.js +428 -83
  67. package/dist/services/SessionBridgeService.js.map +1 -1
  68. package/dist/services/TelemetryService.d.ts +30 -0
  69. package/dist/services/TelemetryService.d.ts.map +1 -1
  70. package/dist/services/TelemetryService.js +42 -0
  71. package/dist/services/TelemetryService.js.map +1 -1
  72. package/dist/services/WorktreeManager.d.ts +29 -2
  73. package/dist/services/WorktreeManager.d.ts.map +1 -1
  74. package/dist/services/WorktreeManager.js +73 -7
  75. package/dist/services/WorktreeManager.js.map +1 -1
  76. package/dist/services/createAgentVariantsOrchestrator.d.ts +0 -8
  77. package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
  78. package/dist/services/createAgentVariantsOrchestrator.js +1 -18
  79. package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
  80. package/dist/utils/portUtils.d.ts +9 -0
  81. package/dist/utils/portUtils.d.ts.map +1 -1
  82. package/dist/utils/portUtils.js +26 -0
  83. package/dist/utils/portUtils.js.map +1 -1
  84. package/dist/utils/skills/claude-skill.d.ts +1 -1
  85. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  86. package/dist/utils/skills/claude-skill.js +31 -12
  87. package/dist/utils/skills/claude-skill.js.map +1 -1
  88. package/dist/utils/skills/cursor-rules.d.ts +1 -1
  89. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  90. package/dist/utils/skills/cursor-rules.js +26 -11
  91. package/dist/utils/skills/cursor-rules.js.map +1 -1
  92. package/dist/utils/skills/shared-variants-protocol.d.ts +28 -0
  93. package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
  94. package/dist/utils/skills/shared-variants-protocol.js +88 -14
  95. package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
  96. package/package.json +3 -2
  97. package/src/ui/dist/assets/main-DREt3GGU.css +1 -0
  98. package/src/ui/dist/assets/main-Dt7ShmjM.js +655 -0
  99. package/src/ui/dist/index.html +2 -2
  100. package/dist/mcp/agent-variants/designCritique.d.ts +0 -167
  101. package/dist/mcp/agent-variants/designCritique.d.ts.map +0 -1
  102. package/dist/mcp/agent-variants/designCritique.js +0 -774
  103. package/dist/mcp/agent-variants/designCritique.js.map +0 -1
  104. package/src/ui/dist/assets/main-B1LAuLK_.js +0 -655
  105. package/src/ui/dist/assets/main-D-YwdYjm.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 = exports.defaultDesignCritiqueRunner = void 0;
6
+ exports.AgentVariantsOrchestrator = void 0;
7
7
  exports.buildStaticPreviewDocument = buildStaticPreviewDocument;
8
8
  const crypto_1 = require("crypto");
9
9
  const events_1 = require("events");
@@ -17,12 +17,12 @@ const devServerError_1 = require("../../utils/devServerError");
17
17
  const errors_1 = require("./errors");
18
18
  const createProjectArtifacts_1 = require("./createProjectArtifacts");
19
19
  const contracts_1 = require("./contracts");
20
+ const runLabel_1 = require("./runLabel");
20
21
  const runPlan_1 = require("./runPlan");
21
22
  const viteReactTs_1 = require("../../services/templates/viteReactTs");
22
23
  const StaticPreviewServer_1 = require("../../services/StaticPreviewServer");
23
24
  const designCatalog_1 = require("../../services/templates/designCatalog");
24
25
  const previewQa_1 = require("./previewQa");
25
- const designCritique_1 = require("./designCritique");
26
26
  const diffQa_1 = require("./diffQa");
27
27
  const VariantHistoryService_1 = require("../../services/VariantHistoryService");
28
28
  const variantContext_1 = require("./variantContext");
@@ -281,94 +281,19 @@ const defaultPreviewQaRunner = ({ html, assetBase }) => (0, previewQa_1.runPrevi
281
281
  ...(assetBase ? { localAssetBasePath: assetBase } : {}),
282
282
  },
283
283
  });
284
- /**
285
- * Production design-critique runner backed by the real GatewayClient + a
286
- * Playwright screenshot. Failure-tolerant: `runDesignCritique` degrades to a
287
- * non-blocking skip on any render/model error.
288
- */
289
- const defaultDesignCritiqueRunner = ({ target, designContextMarkdown, designContract, }) => (0, designCritique_1.runDesignCritique)({ target, designContextMarkdown, designContract });
290
- exports.defaultDesignCritiqueRunner = defaultDesignCritiqueRunner;
291
- /** Merge the structured critique fields (findings/checks/caps) onto a QA
292
- * verdict. Only attaches each field when the critique produced it. */
293
- const attachCritiqueDetail = (qa, outcome) => ({
294
- ...qa,
295
- ...(outcome.scores
296
- ? {
297
- dimensionScores: {
298
- ...outcome.scores,
299
- overall: outcome.overallScore ?? outcome.scores.overall,
300
- },
301
- }
302
- : {}),
303
- ...(outcome.summary ? { designSummary: outcome.summary } : {}),
304
- ...(outcome.findings.length > 0 ? { designFindings: outcome.findings } : {}),
305
- ...(outcome.checks ? { designChecks: outcome.checks } : {}),
306
- ...(outcome.scoreCaps.length > 0
307
- ? { designScoreCaps: outcome.scoreCaps }
308
- : {}),
309
- });
310
- /** Merge passing design scores onto a base QA verdict (status unchanged). */
311
- const attachDesignScores = (base, outcome) => attachCritiqueDetail(base, outcome);
312
- const designScoreFragment = (outcome) => {
313
- const score = outcome.overallScore ?? outcome.scores?.overall;
314
- return score !== undefined ? `overall ${score.toFixed(1)}/10` : 'below bar';
315
- };
316
- /**
317
- * Compose the actionable critique summary handed to a re-leased variant: the
318
- * model's prose plus an explicit, prioritized fix list drawn from the
319
- * critical/major findings so the agent knows exactly what to change.
320
- */
321
- const buildRetryCritiqueSummary = (qa) => {
322
- const base = qa.designSummary ?? qa.summary;
323
- const actionable = (qa.designFindings ?? []).filter((f) => f.severity === 'critical' || f.severity === 'major');
324
- if (actionable.length === 0)
325
- return base;
326
- const fixes = actionable
327
- .map((f) => `- [${f.severity} ${f.category}] ${f.evidence} → ${f.fix}`)
328
- .join('\n');
329
- return `${base}\n\nMust fix before re-reporting:\n${fixes}`;
330
- };
331
- /** One-line, actionable summary of the worst findings for chip/prompt copy. */
332
- const findingsFragment = (outcome) => {
333
- const ranked = outcome.findings.filter((f) => f.severity === 'critical' || f.severity === 'major');
334
- if (ranked.length === 0)
335
- return '';
336
- const first = ranked[0];
337
- const more = ranked.length > 1 ? ` (+${ranked.length - 1} more)` : '';
338
- return `${first.severity} ${first.category}: ${first.evidence}${more}`;
339
- };
340
- /** Build a failed QA verdict for a below-bar variant (drives the re-lease). */
341
- const buildDesignFailureQa = (base, outcome) => {
342
- const findings = findingsFragment(outcome);
343
- const detail = findings || designScoreFragment(outcome);
344
- return attachCritiqueDetail({
345
- status: 'failed',
346
- issues: [
347
- ...base.issues,
348
- {
349
- kind: 'design_quality',
350
- detail,
351
- ...(outcome.summary ? { message: outcome.summary } : {}),
352
- },
353
- ],
354
- summary: outcome.blocked
355
- ? `Design critical: ${detail}.`
356
- : `Design quality below bar (${detail}).`,
357
- ...(base.checkedSource ? { checkedSource: base.checkedSource } : {}),
358
- }, outcome);
284
+ /** Disabled-reason copy for a variant whose preview cannot be opened. */
285
+ const buildViewDisabledReason = (args) => {
286
+ if (args.status === 'failed') {
287
+ return args.qa?.summary ?? 'Generation failed';
288
+ }
289
+ if (args.status === 'cancelled') {
290
+ return 'Variant was cancelled';
291
+ }
292
+ if (args.isSucceeded) {
293
+ return 'Preview is unavailable for this variant';
294
+ }
295
+ return 'Variant is still running';
359
296
  };
360
- /**
361
- * Build a degraded (but committable) QA verdict for a variant that stayed
362
- * below the bar after its one retry. Status is `passed` so commit stays
363
- * enabled — the low score is surfaced via `dimensionScores`/`designSummary`
364
- * rather than hard-blocking the user forever. Only used for non-critical
365
- * failures; an unresolved critical hard-fails instead of degrading.
366
- */
367
- const buildDegradedDesignQa = (base, outcome) => attachCritiqueDetail({
368
- ...base,
369
- status: 'passed',
370
- summary: `Accepted with low design score (${designScoreFragment(outcome)}) after retry.`,
371
- }, outcome);
372
297
  const NOOP_TELEMETRY = { track: () => undefined };
373
298
  const DESIGN_CREATION_ACTION_RE = /\b(create|build|design|make|redesign|refresh|update)\b/i;
374
299
  const DESIGN_CREATION_SURFACE_RE = /\b(home\s?page|landing page|dashboard|screen|site|website|app|page|ui|interface|component|hero)\b/i;
@@ -416,7 +341,6 @@ class AgentVariantsOrchestrator {
416
341
  installDependencies;
417
342
  materializeProject;
418
343
  previewQaRunner;
419
- designCritiqueRunner;
420
344
  switchPreviewPort;
421
345
  setCommittedDevServerHealth;
422
346
  variantHistory;
@@ -460,7 +384,6 @@ class AgentVariantsOrchestrator {
460
384
  this.materializeProject =
461
385
  deps.materializeProject ?? defaultMaterializeProject;
462
386
  this.previewQaRunner = deps.previewQaRunner ?? defaultPreviewQaRunner;
463
- this.designCritiqueRunner = deps.designCritiqueRunner;
464
387
  this.switchPreviewPort = deps.switchPreviewPort;
465
388
  this.setCommittedDevServerHealth = deps.setCommittedDevServerHealth;
466
389
  this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
@@ -661,44 +584,242 @@ class AgentVariantsOrchestrator {
661
584
  }
662
585
  beginStaticPreviewRefinements(args) {
663
586
  const seenVariantIds = new Set();
587
+ // Targets whose in-place refinement could neither be created nor folded
588
+ // into a pending one (e.g. the variant was already sent). We finish the
589
+ // batch's other targets, then RETURN these so the caller can surface them
590
+ // in its response — rather than throwing (which would break the agent poll
591
+ // and lose the already-consumed batch) or silently dropping them.
592
+ const droppedRefinements = [];
593
+ // Work items THIS batch created, so the caller can tell the agent to lease
594
+ // exactly these — letting concurrent runs (several watchers) each pick up
595
+ // their own batch instead of one greedily leasing everything.
596
+ const createdWorkItemIds = [];
664
597
  for (const target of args.targets) {
665
598
  if (seenVariantIds.has(target.variantId))
666
599
  continue;
667
600
  seenVariantIds.add(target.variantId);
668
601
  const currentHtml = this.getStaticPreviewHtml(args.sessionId, target.variantId);
602
+ if (target.count == null) {
603
+ // Plain Apply (no variantRequest) → refine the variant IN PLACE: one
604
+ // edit that overwrites the source, no new sibling cards. Only an
605
+ // explicit "Vary" (which carries a count) fans out new variants.
606
+ try {
607
+ const refineItem = this.store.createRefineVariantWorkItem({
608
+ sessionId: args.sessionId,
609
+ variantId: target.variantId,
610
+ instruction: target.instruction,
611
+ currentHtml,
612
+ targetElement: target.element,
613
+ requestId: target.requestId,
614
+ });
615
+ createdWorkItemIds.push(refineItem.id);
616
+ }
617
+ catch (error) {
618
+ // A refinement is already queued for this variant — fold the newer
619
+ // instruction in rather than dropping it (mirrors the apply queue).
620
+ if (error instanceof errors_1.AgentVariantsError &&
621
+ error.code === 'PENDING_CHANGE_CONFLICT') {
622
+ const folded = this.store.appendPendingRefineVariantInstruction({
623
+ sessionId: args.sessionId,
624
+ variantId: target.variantId,
625
+ instruction: target.instruction,
626
+ });
627
+ if (!folded) {
628
+ // Nothing to fold into — the conflict is terminal (the variant
629
+ // was already sent / is no longer editable). Don't swallow it.
630
+ droppedRefinements.push({
631
+ variantId: target.variantId,
632
+ reason: error.message,
633
+ });
634
+ }
635
+ }
636
+ else if (error instanceof errors_1.AgentVariantsError &&
637
+ error.code === 'VARIANT_NOT_EDITABLE') {
638
+ droppedRefinements.push({
639
+ variantId: target.variantId,
640
+ reason: error.message,
641
+ });
642
+ }
643
+ else {
644
+ throw error;
645
+ }
646
+ }
647
+ continue;
648
+ }
649
+ // "Vary" fans out: create `count` NEW sibling variants seeded from the
650
+ // source HTML + instruction, rather than overwriting the source in place.
651
+ // The agent leases them through the standard request-work path.
669
652
  try {
670
- this.store.createRefineVariantWorkItem({
653
+ const forks = this.store.createForkedStaticPreviewWorkItems({
671
654
  sessionId: args.sessionId,
672
- variantId: target.variantId,
655
+ sourceVariantId: target.variantId,
673
656
  instruction: target.instruction,
657
+ count: target.count,
674
658
  currentHtml,
659
+ targetElement: target.element,
660
+ requestId: target.requestId,
675
661
  });
662
+ createdWorkItemIds.push(...forks.map((item) => item.id));
676
663
  }
677
664
  catch (error) {
665
+ // Terminal conflict — the source is no longer editable, or a variant
666
+ // was already sent for this session (the fork path has nothing to fold
667
+ // into). Surface it structurally like the in-place branch instead of
668
+ // throwing, which would break the agent poll AND lose this batch (the
669
+ // server already consumed it from the FIFO before calling us).
678
670
  if (error instanceof errors_1.AgentVariantsError &&
679
- error.code === 'PENDING_CHANGE_CONFLICT') {
680
- // A refinement is already queued for this variant and the newer
681
- // comment was already consumed from the bridge. Fold it into the
682
- // still-pending item so it isn't silently dropped.
683
- const folded = this.store.appendPendingRefineVariantInstruction({
684
- sessionId: args.sessionId,
671
+ (error.code === 'PENDING_CHANGE_CONFLICT' ||
672
+ error.code === 'VARIANT_NOT_EDITABLE')) {
673
+ droppedRefinements.push({
685
674
  variantId: target.variantId,
686
- instruction: target.instruction,
675
+ reason: error.message,
687
676
  });
688
- if (!folded) {
689
- this.store.createRefineVariantWorkItem({
690
- sessionId: args.sessionId,
691
- variantId: target.variantId,
692
- instruction: target.instruction,
693
- currentHtml,
694
- });
695
- }
696
677
  continue;
697
678
  }
698
679
  throw error;
699
680
  }
700
681
  }
701
682
  this.emitChange();
683
+ return { droppedRefinements, createdWorkItemIds };
684
+ }
685
+ /**
686
+ * Adopt a PAST variant (from a prior session's persisted history) into the
687
+ * active session, rehydrating it from its on-disk static snapshot so the
688
+ * comment / refine / Vary machinery treats it as a live, first-class variant.
689
+ *
690
+ * Triggered when the user makes their first edit on a selected past direction.
691
+ * The store appends an already-`succeeded` `static_preview` work item keyed by
692
+ * the past identity (idempotent); this method registers the matching static-
693
+ * preview record so {@link getStaticPreviewHtml}, {@link getVariants}
694
+ * (preview.kind = 'static_artifact', view enabled), and the
695
+ * `/api/variants/:sid/static/:vid` routes all light up exactly as they do for a
696
+ * freshly-generated static_preview variant.
697
+ *
698
+ * `filesDir` is the absolute path to the persisted `files/` snapshot (resolved
699
+ * by the caller from history) and must hold a renderable `index.html`. Returns
700
+ * the stable active-session `workItemId` so the caller can route the
701
+ * triggering edit to it, plus whether it was already adopted.
702
+ */
703
+ async adoptPastVariant(args) {
704
+ const { assetBase, hasAssets } = await this.resolveAdoptedStaticPreviewFiles(args.filesDir);
705
+ const append = this.store.appendAdoptedStaticVariant({
706
+ sessionId: args.sessionId,
707
+ source: args.source,
708
+ label: args.label,
709
+ description: args.description,
710
+ runLabel: args.runLabel,
711
+ });
712
+ // Register (or re-register, harmlessly) the static-preview record so the
713
+ // snapshot surfaces the adopted variant as a static_artifact.
714
+ const resources = this.ensureResources(args.sessionId);
715
+ resources.staticPreviews.set(append.workItemId, {
716
+ workItemId: append.workItemId,
717
+ briefId: append.briefId,
718
+ assetBase,
719
+ hasAssets,
720
+ });
721
+ this.emitChange();
722
+ return {
723
+ workItemId: append.workItemId,
724
+ alreadyAdopted: !append.created,
725
+ };
726
+ }
727
+ /**
728
+ * Rehydrate a static history variant into the current active session when it
729
+ * can still accept edits; otherwise create a replacement active session whose
730
+ * only visible direction is the selected history artifact.
731
+ */
732
+ async bootstrapPastVariantSession(args) {
733
+ const usableSessionId = this.getUsableActiveSessionId();
734
+ if (usableSessionId) {
735
+ const adopted = await this.adoptPastVariant({
736
+ sessionId: usableSessionId,
737
+ source: args.source,
738
+ filesDir: args.filesDir,
739
+ label: args.label,
740
+ description: args.description,
741
+ runLabel: args.runLabel,
742
+ });
743
+ return {
744
+ sessionId: usableSessionId,
745
+ workItemId: adopted.workItemId,
746
+ alreadyAdopted: adopted.alreadyAdopted,
747
+ bootstrapped: false,
748
+ };
749
+ }
750
+ const { assetBase, hasAssets } = await this.resolveAdoptedStaticPreviewFiles(args.filesDir);
751
+ void this.stopLingeringCommittedDevServers().catch((err) => {
752
+ log.warn('Stopping lingering committed dev servers failed', err);
753
+ });
754
+ const created = this.store.createSessionFromStaticHistoryVariant({
755
+ source: args.source,
756
+ prompt: args.prompt ?? args.description ?? args.label,
757
+ label: args.label,
758
+ description: args.description,
759
+ runLabel: args.runLabel,
760
+ projectContext: args.projectContext,
761
+ });
762
+ this.activeSessionId = created.sessionId;
763
+ const resources = this.ensureResources(created.sessionId);
764
+ resources.staticPreviews.set(created.workItemId, {
765
+ workItemId: created.workItemId,
766
+ briefId: created.briefId,
767
+ assetBase,
768
+ hasAssets,
769
+ });
770
+ this.emitChange();
771
+ return {
772
+ sessionId: created.sessionId,
773
+ workItemId: created.workItemId,
774
+ alreadyAdopted: false,
775
+ bootstrapped: true,
776
+ };
777
+ }
778
+ getUsableActiveSessionId() {
779
+ const sessionId = this.activeSessionId;
780
+ if (!sessionId || !this.store.hasSession(sessionId)) {
781
+ return null;
782
+ }
783
+ const stage = this.store.getStage(sessionId);
784
+ return stage === 'failed' || stage === 'cancelled' ? null : sessionId;
785
+ }
786
+ async resolveAdoptedStaticPreviewFiles(filesDir) {
787
+ const entryFile = path_1.default.join(filesDir, 'index.html');
788
+ try {
789
+ await fs_1.default.promises.access(entryFile, fs_1.default.constants.R_OK);
790
+ }
791
+ catch {
792
+ throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Cannot adopt past variant: no readable index.html under ${filesDir}`);
793
+ }
794
+ let assetBase;
795
+ try {
796
+ assetBase = fs_1.default.realpathSync(filesDir);
797
+ }
798
+ catch {
799
+ assetBase = filesDir;
800
+ }
801
+ let hasAssets = false;
802
+ try {
803
+ hasAssets = fs_1.default.readdirSync(assetBase).some((name) => name !== 'index.html');
804
+ }
805
+ catch {
806
+ hasAssets = false;
807
+ }
808
+ return { assetBase, hasAssets };
809
+ }
810
+ /**
811
+ * True when the variant is an HTML-only static_preview work item. Adopted past
812
+ * variants and fresh `static_preview` variants both qualify. Existing-project
813
+ * `code_gen` variants can still expose a static artifact for preview, but Vary
814
+ * must keep those on the worktree-backed code_gen append path.
815
+ */
816
+ isStaticPreviewVariant(sessionId, workItemId) {
817
+ try {
818
+ return this.store.getWorkItemKind(sessionId, workItemId) === 'static_preview';
819
+ }
820
+ catch {
821
+ return false;
822
+ }
702
823
  }
703
824
  getVariantPick(sessionId) {
704
825
  return this.store.getVariantPick(sessionId);
@@ -756,6 +877,13 @@ class AgentVariantsOrchestrator {
756
877
  const resources = this.resources.get(sessionId);
757
878
  return this.store.getVariants(sessionId).map((variant) => {
758
879
  const port = this.getDevServerPort(sessionId, variant.workItemId);
880
+ // Captured worktree diff (existing-project code_gen). Surfaced as a
881
+ // changed-files count so a diff-only variant (no dev server / static
882
+ // artifact) still reads as a real result in the panel.
883
+ const capturedDiff = resources?.worktrees.get(variant.workItemId)?.diff;
884
+ const changedFilesCount = typeof capturedDiff === 'string' && capturedDiff.length > 0
885
+ ? countDiffFiles(capturedDiff)
886
+ : undefined;
759
887
  // For fresh sessions, static_preview items ARE the variants — look up
760
888
  // directly by workItemId. Fall back to briefId search for existing-project
761
889
  // sessions where code_gen items were paired with companion static_preview items.
@@ -813,6 +941,7 @@ class AgentVariantsOrchestrator {
813
941
  design: enrichDesignSource(variant.design),
814
942
  ...(preview ? { preview } : {}),
815
943
  port,
944
+ ...(changedFilesCount !== undefined ? { changedFilesCount } : {}),
816
945
  ...(qa ? { qa } : {}),
817
946
  ...(previewUnavailable ? { previewUnavailable } : {}),
818
947
  actions: {
@@ -820,9 +949,11 @@ class AgentVariantsOrchestrator {
820
949
  ? { enabled: true }
821
950
  : {
822
951
  enabled: false,
823
- reason: isSucceeded
824
- ? 'Preview is unavailable for this variant'
825
- : 'Variant is still running',
952
+ reason: buildViewDisabledReason({
953
+ status: variant.status,
954
+ isSucceeded,
955
+ qa,
956
+ }),
826
957
  },
827
958
  commit: canCommit
828
959
  ? { enabled: true }
@@ -1011,60 +1142,107 @@ class AgentVariantsOrchestrator {
1011
1142
  if (isSourceGrounded) {
1012
1143
  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.');
1013
1144
  }
1014
- const proposeResult = this.propose({
1145
+ // A second "Vary" on an existing project that still has a live session
1146
+ // ACCUMULATES into that session instead of starting a fresh one that
1147
+ // would orphan the prior variants (and overwrite `activeSessionId`, so
1148
+ // the RHS would only ever show the latest batch). Gated to existing
1149
+ // projects so fresh 0→1 generation is unaffected.
1150
+ const appendSessionId = args.allowAccumulate &&
1151
+ projectContext.kind === 'existing' &&
1152
+ projectContext.projectPath
1153
+ ? this.getAppendableExistingSessionId(projectContext.projectPath)
1154
+ : null;
1155
+ const briefs = this.buildUnifiedBriefs({
1015
1156
  prompt: args.prompt,
1016
- count,
1017
- target: args.target,
1018
- projectContext,
1019
1157
  runLabel: args.runLabel,
1020
- ...(args.sessionId ? { sessionId: args.sessionId } : {}),
1021
- });
1022
- if (proposeResult.stage !== 'awaiting_briefs' ||
1023
- !proposeResult.briefWorkItem) {
1024
- throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `start_variants expected awaiting_briefs from propose, got ${proposeResult.stage}`);
1025
- }
1026
- const briefs = args.briefs
1027
- ? args.briefs.map((b, i) => ({
1028
- briefId: `v${i + 1}`,
1029
- label: b.label,
1030
- body: b.body,
1031
- ...(b.visualReferenceUrl
1032
- ? { visualReferenceUrl: b.visualReferenceUrl }
1033
- : {}),
1034
- }))
1035
- : Array.from({ length: count }, (_, i) => ({
1036
- briefId: `v${i + 1}`,
1037
- label: `Variant ${i + 1}`,
1038
- body: synthesizeUnifiedBriefBody(args.prompt, i + 1, count),
1039
- }));
1040
- this.store.reportBriefs({
1041
- sessionId: proposeResult.sessionId,
1042
- workItemId: proposeResult.briefWorkItem.id,
1043
- attempt: proposeResult.briefWorkItem.attempt,
1044
- briefs,
1045
- });
1046
- const approveResult = await this.approve({
1047
- sessionId: proposeResult.sessionId,
1048
- selections: briefs.map((b) => ({ briefId: b.briefId })),
1049
- });
1050
- this.telemetry.track('agent_variants.unified_started', {
1051
- source: 'mcp',
1052
- sessionId: proposeResult.sessionId,
1053
1158
  count,
1054
- hasTarget: Boolean(args.target),
1055
- targetType: args.target?.type ?? null,
1056
- projectContextKind: projectContext.kind,
1159
+ briefs: args.briefs,
1160
+ uniqueBriefIds: Boolean(appendSessionId),
1057
1161
  });
1058
- // For existing-project sessions, approve() returns the new code_gen
1059
- // ids in codeGenWorkItemIds. For zero-to-one, approve() spawns
1060
- // static_preview items instead — those ids aren't in codeGenWorkItemIds,
1061
- // so read them out of the active-variants snapshot which covers both
1062
- // kinds. Snapshot order mirrors brief order (insertion order).
1063
- const variantIds = projectContext.kind === 'fresh'
1064
- ? this.store
1065
- .getVariants(proposeResult.sessionId)
1066
- .map((v) => v.workItemId)
1067
- : approveResult.codeGenWorkItemIds;
1162
+ let sessionId;
1163
+ let variantIds;
1164
+ let scaffoldBaseWorkItemId = null;
1165
+ if (appendSessionId) {
1166
+ sessionId = appendSessionId;
1167
+ const forkArgs = {
1168
+ sessionId,
1169
+ briefs,
1170
+ runLabel: args.runLabel?.trim() || (0, runLabel_1.deriveRunLabel)(args.prompt),
1171
+ ...(projectContext.kind === 'existing' && projectContext.variantContext
1172
+ ? { variantContext: projectContext.variantContext }
1173
+ : {}),
1174
+ };
1175
+ const { createdWorkItemIds } = args.sourceVariantId
1176
+ ? this.store.createForkedCodeGenWorkItems({
1177
+ ...forkArgs,
1178
+ sourceVariantId: args.sourceVariantId,
1179
+ })
1180
+ : this.store.appendExistingCodeGenVariants(forkArgs);
1181
+ variantIds = createdWorkItemIds;
1182
+ if (args.sourceVariantId) {
1183
+ await this.provisionForkedFromVariantWorktrees(sessionId, args.sourceVariantId, createdWorkItemIds);
1184
+ }
1185
+ else {
1186
+ await this.provisionAppendedExistingWorktrees(sessionId, createdWorkItemIds);
1187
+ }
1188
+ this.telemetry.track('agent_variants.variants_appended', {
1189
+ source: 'mcp',
1190
+ sessionId,
1191
+ count: createdWorkItemIds.length,
1192
+ hasTarget: Boolean(args.target),
1193
+ targetType: args.target?.type ?? null,
1194
+ forkedFromVariantId: args.sourceVariantId ?? null,
1195
+ });
1196
+ this.emitChange();
1197
+ }
1198
+ else {
1199
+ const proposeResult = this.propose({
1200
+ prompt: args.prompt,
1201
+ count,
1202
+ target: args.target,
1203
+ projectContext,
1204
+ runLabel: args.runLabel,
1205
+ ...(args.sessionId ? { sessionId: args.sessionId } : {}),
1206
+ });
1207
+ if (proposeResult.stage !== 'awaiting_briefs' ||
1208
+ !proposeResult.briefWorkItem) {
1209
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `start_variants expected awaiting_briefs from propose, got ${proposeResult.stage}`);
1210
+ }
1211
+ this.store.reportBriefs({
1212
+ sessionId: proposeResult.sessionId,
1213
+ workItemId: proposeResult.briefWorkItem.id,
1214
+ attempt: proposeResult.briefWorkItem.attempt,
1215
+ briefs,
1216
+ });
1217
+ const approveResult = await this.approve({
1218
+ sessionId: proposeResult.sessionId,
1219
+ selections: briefs.map((b) => ({ briefId: b.briefId })),
1220
+ });
1221
+ this.telemetry.track('agent_variants.unified_started', {
1222
+ source: 'mcp',
1223
+ sessionId: proposeResult.sessionId,
1224
+ count,
1225
+ hasTarget: Boolean(args.target),
1226
+ targetType: args.target?.type ?? null,
1227
+ projectContextKind: projectContext.kind,
1228
+ });
1229
+ sessionId = proposeResult.sessionId;
1230
+ scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId ?? null;
1231
+ // For existing-project sessions, approve() returns the new code_gen
1232
+ // ids in codeGenWorkItemIds. For zero-to-one, approve() spawns
1233
+ // static_preview items instead — those ids aren't in codeGenWorkItemIds,
1234
+ // so read them out of the active-variants snapshot which covers both
1235
+ // kinds. Snapshot order mirrors brief order (insertion order).
1236
+ variantIds =
1237
+ projectContext.kind === 'fresh'
1238
+ ? this.store.getVariants(sessionId).map((v) => v.workItemId)
1239
+ : approveResult.codeGenWorkItemIds;
1240
+ }
1241
+ // Stamp the UI correlation id so the panel maps its optimistic placeholders
1242
+ // to these real cards (the same mechanism the static-preview fork uses).
1243
+ if (args.requestId) {
1244
+ this.store.assignVariantRequestId(sessionId, variantIds, args.requestId);
1245
+ }
1068
1246
  const variants = variantIds.map((workItemId, i) => ({
1069
1247
  variantId: workItemId,
1070
1248
  briefId: briefs[i].briefId,
@@ -1076,22 +1254,111 @@ class AgentVariantsOrchestrator {
1076
1254
  let leasedWorkItems;
1077
1255
  if (args.leaseOwner) {
1078
1256
  const lease = this.requestWork({
1079
- sessionId: proposeResult.sessionId,
1257
+ sessionId,
1080
1258
  leaseOwner: args.leaseOwner,
1259
+ // Append: lease ONLY the new batch so a still-leasable prior item
1260
+ // (e.g. a re-leased one) isn't swept into this Vary's lease.
1261
+ ...(appendSessionId ? { workItemIds: variantIds } : {}),
1081
1262
  });
1082
1263
  leaseId = lease.leaseId;
1083
1264
  leaseTtlMs = lease.leaseTtlMs;
1084
1265
  leasedWorkItems = lease.leasedWorkItems;
1085
1266
  }
1086
1267
  return {
1087
- sessionId: proposeResult.sessionId,
1268
+ sessionId,
1088
1269
  variants,
1089
- scaffoldBaseWorkItemId: approveResult.scaffoldBaseWorkItemId ?? null,
1270
+ scaffoldBaseWorkItemId,
1090
1271
  leaseId,
1091
1272
  leaseTtlMs,
1092
1273
  leasedWorkItems,
1093
1274
  };
1094
1275
  }
1276
+ /**
1277
+ * Build the per-variant briefs for a unified start. Agent-supplied briefs win
1278
+ * verbatim. The fallback derives a short run-intent label from the prompt
1279
+ * (e.g. "make it brighter" → "Brighter 1/2/…") rather than an indistinct
1280
+ * "Variant N", so two sequential Vary batches stay distinguishable in the
1281
+ * Directions list. `uniqueBriefIds` is set for the append path so the new
1282
+ * batch's briefIds can't collide with the live session's existing ones.
1283
+ */
1284
+ buildUnifiedBriefs(args) {
1285
+ const briefId = (i) => args.uniqueBriefIds ? `v-${(0, crypto_1.randomUUID)().slice(0, 8)}` : `v${i + 1}`;
1286
+ if (args.briefs) {
1287
+ return args.briefs.map((b, i) => ({
1288
+ briefId: briefId(i),
1289
+ label: b.label,
1290
+ body: b.body,
1291
+ ...(b.visualReferenceUrl
1292
+ ? { visualReferenceUrl: b.visualReferenceUrl }
1293
+ : {}),
1294
+ }));
1295
+ }
1296
+ const runLabel = args.runLabel?.trim() || (0, runLabel_1.deriveRunLabel)(args.prompt);
1297
+ return Array.from({ length: args.count }, (_, i) => ({
1298
+ briefId: briefId(i),
1299
+ label: args.count > 1 ? `${runLabel} ${i + 1}` : runLabel,
1300
+ body: synthesizeUnifiedBriefBody(args.prompt, i + 1, args.count),
1301
+ }));
1302
+ }
1303
+ /**
1304
+ * The active sessionId IF it is an existing-project session for `projectPath`
1305
+ * that can still accept appended variants — i.e. not committed, not
1306
+ * cancelled/failed. Returns null otherwise, so start_variants falls back to
1307
+ * creating a fresh session (first Vary, a different project, or after the
1308
+ * prior session was committed/cancelled).
1309
+ */
1310
+ getAppendableExistingSessionId(projectPath) {
1311
+ const sessionId = this.activeSessionId;
1312
+ if (!sessionId || !this.store.hasSession(sessionId))
1313
+ return null;
1314
+ const ctx = this.store.getProjectContext(sessionId);
1315
+ if (ctx.kind !== 'existing' || ctx.projectPath !== projectPath)
1316
+ return null;
1317
+ if (this.store.getVariantPick(sessionId))
1318
+ return null;
1319
+ const stage = this.store.getStage(sessionId);
1320
+ if (stage === 'cancelled' || stage === 'failed')
1321
+ return null;
1322
+ return sessionId;
1323
+ }
1324
+ /**
1325
+ * Provision a worktree for each appended existing-project variant. Mirrors
1326
+ * {@link provisionExistingWorktrees} but only for the newly appended ids, and
1327
+ * passes a per-batch `instanceKeyPrefix` so the new worktree dirs do not
1328
+ * collide with the first batch's `${sessionId}-${i}` paths.
1329
+ */
1330
+ async provisionAppendedExistingWorktrees(sessionId, workItemIds) {
1331
+ if (workItemIds.length === 0)
1332
+ return;
1333
+ const resources = this.ensureResources(sessionId);
1334
+ const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1335
+ const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1336
+ workItemIds.forEach((id, i) => {
1337
+ resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
1338
+ });
1339
+ }
1340
+ /**
1341
+ * Provision worktrees for a code_gen Vary forked from a parent variant.
1342
+ * Copies the parent's worktree tree so each sibling starts from the parent's
1343
+ * diff rather than git HEAD / Original.
1344
+ */
1345
+ async provisionForkedFromVariantWorktrees(sessionId, sourceVariantId, workItemIds) {
1346
+ if (workItemIds.length === 0)
1347
+ return;
1348
+ const sourcePath = this.getWorktreePath(sessionId, sourceVariantId);
1349
+ if (!sourcePath) {
1350
+ throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', `Cannot fork from variant ${sourceVariantId}: no worktree path`);
1351
+ }
1352
+ if (!this.worktrees.forkWorktreesFrom) {
1353
+ throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement forkWorktreesFrom');
1354
+ }
1355
+ const resources = this.ensureResources(sessionId);
1356
+ const instanceKeyPrefix = `f${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1357
+ const paths = await withProvisionTimeout(this.worktrees.forkWorktreesFrom(sourcePath, sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1358
+ workItemIds.forEach((id, i) => {
1359
+ resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
1360
+ });
1361
+ }
1095
1362
  async reportComplete(args) {
1096
1363
  // Cooperative abort short-circuit: if the work item was removed/cancelled
1097
1364
  // while in flight (delete-while-loading, cancel_variant, session cancel),
@@ -1129,10 +1396,11 @@ class AgentVariantsOrchestrator {
1129
1396
  const qaEval = workItemKind === 'refine_variant'
1130
1397
  ? null
1131
1398
  : await this.evaluateQaForReport(args);
1132
- // A `retry` verdict re-leases the variant for one aesthetic regeneration
1133
- // instead of recording success — short-circuit the normal record path.
1399
+ // A `retry` verdict re-leases the variant for one regeneration (the
1400
+ // existing-project diff task-fit gate) instead of recording success —
1401
+ // short-circuit the normal record path.
1134
1402
  if (qaEval?.decision === 'retry') {
1135
- return this.handleDesignCritiqueRetry(args, qaEval.qa);
1403
+ return this.handleQaRegenRetry(args, qaEval.qa);
1136
1404
  }
1137
1405
  let effectiveArgs = qaEval?.decision === 'fail_terminal' ? qaEval.overrideArgs : args;
1138
1406
  // For a successful refine_variant, swap the refined static preview into
@@ -2227,11 +2495,9 @@ class AgentVariantsOrchestrator {
2227
2495
  }
2228
2496
  return null;
2229
2497
  }
2230
- // Resolve the objective base QA (static_preview only) and the design
2231
- // critique render target for this deliverable kind.
2498
+ // Resolve the objective base QA for this deliverable kind.
2232
2499
  let baseQa = null;
2233
- let critiqueTarget = null;
2234
- let checkedSource;
2500
+ const checkedSource = undefined;
2235
2501
  if (kind === 'static_preview') {
2236
2502
  const parsed = parseStaticPreviewOutput(normalizeOutput(args.output));
2237
2503
  if (!parsed)
@@ -2241,22 +2507,16 @@ class AgentVariantsOrchestrator {
2241
2507
  projectContext,
2242
2508
  });
2243
2509
  baseQa = await this.runPreviewQaSafely(args, parsed.html, assetBase);
2244
- critiqueTarget = {
2245
- kind: 'html',
2246
- html: buildStaticPreviewDocument({ html: parsed.html }),
2247
- ...(assetBase ? { assetBase } : {}),
2248
- };
2249
2510
  }
2250
2511
  else if (kind === 'code_gen') {
2251
- // Fresh vite_app variants run agent-authored dev-server code. Do not
2252
- // navigate or request that server from the critique sandbox.
2512
+ // Fresh vite_app variants run agent-authored dev-server code. No
2513
+ // preview document to inspect behave exactly as before.
2253
2514
  return null;
2254
2515
  }
2255
2516
  else {
2256
2517
  return null;
2257
2518
  }
2258
- // Objective base QA failure is terminal exactly as before — never run the
2259
- // aesthetic critique on a variant that's already broken.
2519
+ // Objective base QA failure is terminal exactly as before.
2260
2520
  if (baseQa && baseQa.status === 'failed') {
2261
2521
  this.memoizeQa(args.sessionId, args.workItemId, baseQa);
2262
2522
  return {
@@ -2276,54 +2536,8 @@ class AgentVariantsOrchestrator {
2276
2536
  summary: 'Preview QA passed — dev server reachable.',
2277
2537
  checkedSource,
2278
2538
  };
2279
- // No design critique wired → behave exactly like objective-only QA.
2280
- if (!this.designCritiqueRunner) {
2281
- this.memoizeQa(args.sessionId, args.workItemId, passedBase);
2282
- return { decision: 'pass', qa: passedBase };
2283
- }
2284
- const outcome = await this.runDesignCritiqueSafely({
2285
- sessionId: args.sessionId,
2286
- workItemId: args.workItemId,
2287
- target: critiqueTarget,
2288
- });
2289
- // Critique skipped (render/model unavailable) → non-blocking, keep base.
2290
- if (!outcome.ran) {
2291
- this.memoizeQa(args.sessionId, args.workItemId, passedBase);
2292
- return { decision: 'pass', qa: passedBase };
2293
- }
2294
- if (outcome.passed) {
2295
- const qa = attachDesignScores(passedBase, outcome);
2296
- this.memoizeQa(args.sessionId, args.workItemId, qa);
2297
- return { decision: 'pass', qa };
2298
- }
2299
- // Below the bar. First failure → re-lease for one regeneration.
2300
- const resources = this.ensureResources(args.sessionId);
2301
- if (!resources.designCritiqueRetried.has(args.workItemId)) {
2302
- const qa = buildDesignFailureQa(passedBase, outcome);
2303
- return { decision: 'retry', qa };
2304
- }
2305
- // Retry already spent. A `critical` finding is a hard gate: an unusable
2306
- // variant must fail terminally (commit disabled) rather than degrade into
2307
- // a committable accept — a high vibe/brand score can never carry it past a
2308
- // usability-breaking defect. Non-critical (score- or major-driven) failures
2309
- // degrade to committable so a single fixable flaw doesn't reject a
2310
- // promising 0→1 direction forever.
2311
- if (outcome.blocked) {
2312
- const qa = buildDesignFailureQa(passedBase, outcome);
2313
- this.memoizeQa(args.sessionId, args.workItemId, qa);
2314
- return {
2315
- decision: 'fail_terminal',
2316
- qa,
2317
- overrideArgs: {
2318
- ...args,
2319
- status: 'failed',
2320
- error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
2321
- },
2322
- };
2323
- }
2324
- const qa = buildDegradedDesignQa(passedBase, outcome);
2325
- this.memoizeQa(args.sessionId, args.workItemId, qa);
2326
- return { decision: 'degrade', qa };
2539
+ this.memoizeQa(args.sessionId, args.workItemId, passedBase);
2540
+ return { decision: 'pass', qa: passedBase };
2327
2541
  }
2328
2542
  async evaluateExistingCodeGenQa(args) {
2329
2543
  const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
@@ -2425,6 +2639,10 @@ class AgentVariantsOrchestrator {
2425
2639
  ],
2426
2640
  summary: 'No generated content was added for this design variant; regenerate it with real homepage UI changes.',
2427
2641
  };
2642
+ const sessionResources = this.ensureResources(args.sessionId);
2643
+ if (!sessionResources.qaRegenRetried.has(args.workItemId)) {
2644
+ return { decision: 'retry', qa };
2645
+ }
2428
2646
  this.memoizeQa(args.sessionId, args.workItemId, qa);
2429
2647
  return {
2430
2648
  decision: 'fail_terminal',
@@ -2458,93 +2676,33 @@ class AgentVariantsOrchestrator {
2458
2676
  };
2459
2677
  }
2460
2678
  }
2461
- /** Run the injected design critique runner; a thrown runner degrades to a
2462
- * non-blocking skip so the aesthetic layer can never break the gate. */
2463
- async runDesignCritiqueSafely(args) {
2464
- try {
2465
- const designContextMarkdown = this.resolveBoundDesignMarkdown(args.sessionId, args.workItemId);
2466
- const designContract = this.resolveDesignContract(args.sessionId, args.workItemId);
2467
- return await this.designCritiqueRunner({
2468
- sessionId: args.sessionId,
2469
- workItemId: args.workItemId,
2470
- target: args.target,
2471
- designContextMarkdown,
2472
- designContract,
2473
- });
2474
- }
2475
- catch (err) {
2476
- const message = err instanceof Error ? err.message : String(err);
2477
- log.warn(`Design critique runner threw for ${args.sessionId}/${args.workItemId}: ${message}`);
2478
- return {
2479
- ran: false,
2480
- passed: true,
2481
- blocked: false,
2482
- findings: [],
2483
- scoreCaps: [],
2484
- skippedReason: `runner: ${message}`,
2485
- };
2486
- }
2487
- }
2488
- /** Resolve the DESIGN.md markdown bound to a variant work item, if any. */
2489
- resolveBoundDesignMarkdown(sessionId, workItemId) {
2490
- try {
2491
- const input = this.store.getWorkItemInput(sessionId, workItemId);
2492
- return resolveDesignArtifact(input.designContextEntry)?.markdown;
2493
- }
2494
- catch {
2495
- return undefined;
2496
- }
2497
- }
2498
- /**
2499
- * Derive the task contract (artifact type + explicit requirements) for a
2500
- * variant from the session prompt and the variant's brief body, so the
2501
- * critique judges the variant against what was actually requested. Returns
2502
- * undefined when no request copy is available.
2503
- */
2504
- resolveDesignContract(sessionId, workItemId) {
2505
- try {
2506
- const prompt = this.store.getPrompt(sessionId);
2507
- const input = this.store.getWorkItemInput(sessionId, workItemId);
2508
- const contract = (0, designCritique_1.buildDesignContract)({
2509
- prompt,
2510
- brief: input.briefBody,
2511
- });
2512
- if (contract.requirements.length === 0)
2513
- return undefined;
2514
- return contract;
2515
- }
2516
- catch {
2517
- return undefined;
2518
- }
2519
- }
2520
2679
  memoizeQa(sessionId, workItemId, qa) {
2521
2680
  this.ensureResources(sessionId).qaResults.set(workItemId, qa);
2522
2681
  }
2523
2682
  /**
2524
2683
  * Handle a `retry` QA verdict: consume the agent's report, re-open the
2525
- * variant work item so it can be re-leased and regenerated against the
2526
- * critique, and record that it has now used its single retry.
2684
+ * variant work item so it can be re-leased and regenerated against the QA
2685
+ * notes, and record that it has now used its single retry.
2527
2686
  */
2528
- async handleDesignCritiqueRetry(args, qa) {
2687
+ async handleQaRegenRetry(args, qa) {
2529
2688
  const resources = this.ensureResources(args.sessionId);
2530
- const result = this.store.requeueForDesignCritique({
2689
+ const result = this.store.requeueForRegeneration({
2531
2690
  sessionId: args.sessionId,
2532
2691
  workItemId: args.workItemId,
2533
2692
  leaseId: args.leaseId,
2534
2693
  attempt: args.attempt,
2535
- critique: {
2536
- summary: buildRetryCritiqueSummary(qa),
2537
- dimensionScores: qa.dimensionScores,
2694
+ qaRetry: {
2695
+ summary: qa.summary,
2538
2696
  },
2539
2697
  });
2540
- resources.designCritiqueRetried.add(args.workItemId);
2698
+ resources.qaRegenRetried.add(args.workItemId);
2541
2699
  resources.qaResults.delete(args.workItemId);
2542
2700
  this.telemetry.track('agent_variants.variant_design_requeued', {
2543
2701
  source: 'mcp',
2544
2702
  sessionId: args.sessionId,
2545
2703
  workItemId: args.workItemId,
2546
2704
  attempt: args.attempt,
2547
- overall: qa.dimensionScores?.overall ?? null,
2705
+ overall: null,
2548
2706
  });
2549
2707
  this.emitChange();
2550
2708
  // Carry the requeue signal + critique so the tool layer can tell the agent
@@ -3872,7 +4030,7 @@ class AgentVariantsOrchestrator {
3872
4030
  startedAt: Date.now(),
3873
4031
  leasedAt: new Map(),
3874
4032
  qaResults: new Map(),
3875
- designCritiqueRetried: new Set(),
4033
+ qaRegenRetried: new Set(),
3876
4034
  previewFailures: new Map(),
3877
4035
  vitePreservedSiblings: false,
3878
4036
  };
@@ -4855,13 +5013,21 @@ const buildExistingProjectDevCommand = (packageManager, devCommand, port) => {
4855
5013
  if (packageManager === 'npm') {
4856
5014
  return {
4857
5015
  cmd: 'npm',
4858
- args: ['run', devCommand, '--', '--port', String(port)],
5016
+ args: [
5017
+ 'run',
5018
+ devCommand,
5019
+ '--',
5020
+ '--port',
5021
+ String(port),
5022
+ '--host',
5023
+ FRESH_DEV_SERVER_HOST,
5024
+ ],
4859
5025
  env,
4860
5026
  };
4861
5027
  }
4862
5028
  return {
4863
5029
  cmd: packageManager,
4864
- args: [devCommand, '--port', String(port)],
5030
+ args: [devCommand, '--port', String(port), '--host', FRESH_DEV_SERVER_HOST],
4865
5031
  env,
4866
5032
  };
4867
5033
  };