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.startMCPServer = exports.createRivetMcp = exports.buildStaticPreviewRefineResponse = exports.collectStaticPreviewRefineTargetInstructions = exports.buildVariantRequestBatchResponse = exports.buildMixedVariantBatchResponse = exports.startParentProcessMonitor = exports.configureMcpStdioLogging = void 0;
6
+ exports.startMCPServer = exports.createRivetMcp = exports.buildLeasedVariantResponse = exports.buildStaticPreviewRefineResponse = exports.collectStaticPreviewRefineTargetInstructions = exports.buildApplyChangesResponse = exports.buildNoPendingChangesResponse = exports.resolveLeasePickupRequestId = exports.buildVariantRequestBatchResponse = exports.buildMixedVariantBatchResponse = exports.startParentProcessMonitor = exports.buildVisualEditorWatchAction = exports.configureMcpStdioLogging = void 0;
7
7
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
8
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
9
  const v3_1 = require("zod/v3");
@@ -99,6 +99,17 @@ async function stopChildProcess(child, timeoutMs = DEV_SERVER_STOP_TIMEOUT_MS) {
99
99
  }
100
100
  await waitForExit;
101
101
  }
102
+ /** Builds the required host-agent action that keeps a visual editor session listening. */
103
+ const buildVisualEditorWatchAction = (sessionId, timing = 'immediate') => ({
104
+ tool: 'watch_for_changes',
105
+ arguments: { sessionId },
106
+ required: true,
107
+ timing,
108
+ loopUntil: 'close_visual_editor',
109
+ onTimeout: 'call_again',
110
+ afterHandlingChanges: 'call_again',
111
+ });
112
+ exports.buildVisualEditorWatchAction = buildVisualEditorWatchAction;
102
113
  const DEV_SERVER_STOP_TIMEOUT_MS = 5000;
103
114
  const PARENT_PROCESS_MONITOR_INTERVAL_MS = 2000;
104
115
  const SUPERSEDED_CHANGES_MESSAGE = 'Queued changes were superseded by newer user input. Call watch_for_changes again.';
@@ -403,22 +414,68 @@ const requestHasResolvedReferences = (request) => request.changes.some((c) => c.
403
414
  Array.isArray(c.resolvedReferences) &&
404
415
  c.resolvedReferences.length > 0);
405
416
  const VARIANT_REQUEST_GUIDE = 'This batch is a Rivet variant request — do NOT edit files directly. ' +
406
- 'The pending change is a single CommentChangeItem with `variantRequest.count`. ' +
417
+ 'The pending change is a single CommentChangeItem; `variantRequest.count` is only the editor\'s default placeholder count — YOU own the real number. Decide how many directions to generate from the user\'s `instruction` text (honor an explicit ask like "give me 5"; otherwise default to 3-4). ' +
407
418
  "Derive the structured `start_variants.target` from the comment's `element` using this priority: " +
408
419
  '(a) `{ type: "file", ref: filePaths[0] }` when `filePaths.length === 1`; otherwise ' +
409
- '(b) `{ type: "selector", ref: `[data-rivet-id="${rivetId}"]` }` when `rivetId` is present, then ' +
410
- '(c) `{ type: "selector", ref: `#${id}` }` when `id` has no whitespace, then ' +
411
- '(d) `{ type: "selector", ref: xpath }`, then ' +
412
- '(e) `{ type: "selector", ref: `.${token}` }` for a single-token `className`. ' +
420
+ '(b) `{ type: "element", ref: `[data-rivet-id="${rivetId}"]` }` when `rivetId` is present, then ' +
421
+ '(c) `{ type: "element", ref: `#${id}` }` when `id` has no whitespace, then ' +
422
+ '(d) `{ type: "element", ref: xpath }`, then ' +
423
+ '(e) `{ type: "element", ref: `.${token}` }` for a single-token `className`. ' +
424
+ 'When you set a `target` from the comment\'s `element`, scope each variant\'s change to THAT element and its immediate section/card — do not restyle the rest of the page. ' +
413
425
  'If the comment has imageCount > 0, inspect the following image content items before calling `start_variants`; pass each image through `userContext` as `{ transport: "upload", dataUrl, label: "Reference image N", userIntent: "Inline variant visual reference" }` when your client exposes a data URL, or pass concise visual observations as `{ transport: "text", content, label: "Reference image observations", userIntent: "Inline variant visual reference" }` when it does not. ' +
414
- 'Then call `start_variants` with `{ mode: "existing", prompt: instruction, count: variantRequest.count, target, userContext }`. Do NOT use the older `propose_variants` chain for this path. `start_variants` returns leased work items; generate each variant in its worktree and call `report_variant_complete` for each leased item. ' +
426
+ 'Then call `start_variants` with `{ mode: "existing", prompt: instruction, target, userContext, requestId, briefs }`, passing this batch\'s `requestId` through verbatim so the editor can map its loading placeholders to the new cards. Set the count by supplying `briefs` (one distinct `{ label, body }` per variant — `briefs.length` is the count, and each card is named by what it changes); the editor seeds a few placeholders and reconciles to however many you actually generate. Do NOT use the older `propose_variants` chain for this path. ' +
427
+ 'ACCUMULATION: when a variants session is already live for this project, calling `start_variants` again does NOT discard it — Rivet APPENDS the new directions to that same active session (they show up alongside the existing ones), so just call `start_variants` per Vary; never try to reuse a prior sessionId or worry about orphaning earlier variants. ' +
428
+ '`start_variants` returns leased work items; generate each variant in its worktree and call `report_variant_complete` for each leased item. ' +
415
429
  'After the user picks a variant via `commit_variant`, STOP and tell the user their variant has been committed.';
416
430
  const VARIANT_MIXED_BATCH_GUIDE = 'Mixed batch rejected: a `variantRequest` comment must be the only item in its batch. ' +
417
431
  'Ask the user to send the variant request on its own (clear other pending changes first) and retry.';
418
- const STATIC_PREVIEW_REFINE_GUIDE = 'STATIC_PREVIEW_REFINE_GUIDE: These changes target a static_preview variant. ' +
419
- 'Do NOT edit base project files. Group changes by staticPreviewTarget, then call ' +
420
- "`continue_variants(action:'refine_variant', variantId, instruction)` once per distinct target with that group's combined instruction. " +
421
- 'Regenerate each variant HTML, then call `report_variant_complete` for each leased refine work item with output.html containing the full updated static HTML.';
432
+ // Shared preamble for both static-preview refine guides (fork vs in-place).
433
+ // Keeps the STATIC_PREVIEW_REFINE_GUIDE marker, the lease call, and the
434
+ // targetElement scoping rule consistent across both flows.
435
+ const STATIC_PREVIEW_REFINE_GUIDE_PREAMBLE = 'STATIC_PREVIEW_REFINE_GUIDE: These changes target static_preview variant(s). ' +
436
+ 'Do NOT edit base project files. ' +
437
+ "Lease ONLY this batch's work items: call `continue_variants({ action: 'request_work', workItemIds: <the response.workItemIds array> })`. " +
438
+ 'Passing workItemIds (rather than a bare requestedLeaseCount) keeps concurrent runs from leasing each other\'s work, so several batches can be handled in parallel. ' +
439
+ 'When input.targetElement is present it is the specific element the user selected (match it in the HTML by tagName/className/id/textContent): ' +
440
+ 'scope the change to THAT element and its immediate section/card — do not restyle the rest of the page. ';
441
+ // "Vary" flow: the server forked N NEW sibling variants; the original is left
442
+ // untouched and each fork gets a synthesized parent+change name.
443
+ const STATIC_PREVIEW_FORK_GUIDE = STATIC_PREVIEW_REFINE_GUIDE_PREAMBLE +
444
+ 'Do NOT overwrite the original variant. ' +
445
+ 'The server has already created the requested number of NEW sibling variants per target — each is a fresh ' +
446
+ 'static_preview work item seeded from the source variant (input.currentHtml) plus your instruction (input.instruction). ' +
447
+ 'For each leased static_preview work item, generate a NEW variation of input.currentHtml that applies input.instruction. ' +
448
+ 'Then call `report_variant_complete` with output.html containing the full updated static HTML, ' +
449
+ 'AND a synthesized `title` (≤ 6 words) + one-sentence `description` that combine the parent variant ' +
450
+ '(input.sourceLabel / input.sourceBody) with the change (input.instruction) — e.g. "Icon grid — red hover". ' +
451
+ 'Each leased item becomes its own new variant card; the original variant is left unchanged.';
452
+ // "Apply" flow: the server created a single in-place refinement work item that
453
+ // UPDATES THE SAME variant — it is NOT a new sibling, so the variant keeps its
454
+ // existing name and no synthesized title/description is reported.
455
+ const STATIC_PREVIEW_INPLACE_REFINE_GUIDE = STATIC_PREVIEW_REFINE_GUIDE_PREAMBLE +
456
+ 'The server has created an IN-PLACE refinement work item that UPDATES THE SAME variant per target — ' +
457
+ 'it is NOT a new sibling. Lease it, then edit input.currentHtml to apply input.instruction, ' +
458
+ 'preserving everything else. ' +
459
+ 'Then call `report_variant_complete` with output.html containing the full updated static HTML. ' +
460
+ 'Do NOT pass a new `title`/`description` — the variant keeps its existing name. ' +
461
+ 'The edited HTML replaces the existing variant in place.';
462
+ /**
463
+ * Pick the guide that matches what the server actually did with this batch:
464
+ * a "Vary" target carries a `count` (the server forked N siblings), while a
465
+ * plain "Apply" has no count (the server created one in-place refinement that
466
+ * overwrites the source). A mixed batch falls back to the fork guide, whose
467
+ * lease+report steps still cover the count-bearing targets.
468
+ */
469
+ const selectStaticPreviewRefineGuide = (targetInstructions) => targetInstructions.some(({ count }) => count != null)
470
+ ? STATIC_PREVIEW_FORK_GUIDE
471
+ : STATIC_PREVIEW_INPLACE_REFINE_GUIDE;
472
+ const buildLeasePayloadFields = (lease) => ({
473
+ leaseId: lease.leaseId,
474
+ requestId: lease.requestId,
475
+ kind: lease.kind,
476
+ mergedRequestIds: lease.mergedRequestIds,
477
+ mergedFrom: lease.mergedFrom,
478
+ });
422
479
  const buildSupersededChangesResponse = () => ({
423
480
  content: [
424
481
  {
@@ -451,11 +508,12 @@ const buildMixedVariantBatchResponse = (rejectedRequest) => {
451
508
  };
452
509
  };
453
510
  exports.buildMixedVariantBatchResponse = buildMixedVariantBatchResponse;
454
- const buildVariantRequestBatchResponse = (consumedRequest) => {
455
- if (!consumedRequest) {
511
+ const buildVariantRequestBatchResponse = (intent, lease) => {
512
+ if (!intent) {
456
513
  return buildSupersededChangesResponse();
457
514
  }
458
- const { changes, imageContent } = (0, changeBatchClassification_1.extractImages)(consumedRequest.changes);
515
+ const { changes, imageContent } = (0, changeBatchClassification_1.extractImages)(intent.changes);
516
+ const requestId = intent.agentRunId ?? intent.traceId;
459
517
  return {
460
518
  content: [
461
519
  {
@@ -464,7 +522,10 @@ const buildVariantRequestBatchResponse = (consumedRequest) => {
464
522
  hasChanges: true,
465
523
  kind: 'variant_request',
466
524
  changes,
467
- sourceFiles: consumedRequest.sourceFiles,
525
+ sourceFiles: intent.sourceFiles,
526
+ ...(requestId ? { requestId } : {}),
527
+ ...(intent.target ? { target: intent.target } : {}),
528
+ ...(lease ? buildLeasePayloadFields(lease) : {}),
468
529
  applyGuide: VARIANT_REQUEST_GUIDE,
469
530
  }),
470
531
  },
@@ -473,10 +534,71 @@ const buildVariantRequestBatchResponse = (consumedRequest) => {
473
534
  };
474
535
  };
475
536
  exports.buildVariantRequestBatchResponse = buildVariantRequestBatchResponse;
537
+ /**
538
+ * Resolves whether `watch_for_changes` / `get_pending_changes` should pass
539
+ * `requestId` into lease pickup. requestId is only for idempotent re-poll of
540
+ * the same in-flight lease — omit it after ack so FIFO can pick the next batch.
541
+ */
542
+ const resolveLeasePickupRequestId = (args) => args.refresh_git || args.ack_batch ? undefined : args.requestId;
543
+ exports.resolveLeasePickupRequestId = resolveLeasePickupRequestId;
544
+ /**
545
+ * Builds a poll response for an empty apply queue.
546
+ */
547
+ const buildNoPendingChangesResponse = (params = {}) => ({
548
+ content: [
549
+ {
550
+ type: 'text',
551
+ text: JSON.stringify({
552
+ hasChanges: false,
553
+ ...(params.message ? { message: params.message } : {}),
554
+ ...(params.devServerHealth !== undefined
555
+ ? { devServerHealth: params.devServerHealth }
556
+ : {}),
557
+ ...(params.pendingVariantRequests !== undefined
558
+ ? { pendingVariantRequests: params.pendingVariantRequests }
559
+ : {}),
560
+ }),
561
+ },
562
+ ],
563
+ });
564
+ exports.buildNoPendingChangesResponse = buildNoPendingChangesResponse;
565
+ /**
566
+ * Builds a poll response for a normal apply batch.
567
+ */
568
+ const buildApplyChangesResponse = (params) => {
569
+ const { changes, imageContent } = (0, changeBatchClassification_1.extractImages)(params.intent.changes);
570
+ return {
571
+ content: [
572
+ {
573
+ type: 'text',
574
+ text: JSON.stringify({
575
+ hasChanges: true,
576
+ kind: 'apply',
577
+ changes,
578
+ sourceFiles: params.intent.sourceFiles,
579
+ ...(params.intent.target ? { target: params.intent.target } : {}),
580
+ ...(params.intent.worktreePath
581
+ ? { worktreePath: params.intent.worktreePath }
582
+ : {}),
583
+ ...(params.lease ? buildLeasePayloadFields(params.lease) : {}),
584
+ applyGuide: buildApplyGuide(params.intent.worktreePath, requestHasResolvedReferences(params.intent)),
585
+ ...(params.pendingVariantRequests !== undefined
586
+ ? { pendingVariantRequests: params.pendingVariantRequests }
587
+ : {}),
588
+ }),
589
+ },
590
+ ...imageContent,
591
+ ],
592
+ };
593
+ };
594
+ exports.buildApplyChangesResponse = buildApplyChangesResponse;
476
595
  const staticPreviewTargetKey = (target) => `${target.sessionId}:${target.variantId}`;
477
596
  const getCommentStaticPreviewTarget = (comment, fallbackTarget) => comment.staticPreviewTarget ?? fallbackTarget;
478
597
  const collectStaticPreviewRefineTargetInstructions = (request) => {
479
598
  const groups = new Map();
599
+ // Per-request correlation id: each Vary/Apply is dispatched as its own batch,
600
+ // so the batch's agentRunId (or traceId) uniquely identifies this UI request.
601
+ const requestId = request.agentRunId ?? request.traceId;
480
602
  for (const change of request.changes) {
481
603
  if (change.kind !== 'comment')
482
604
  continue;
@@ -484,21 +606,29 @@ const collectStaticPreviewRefineTargetInstructions = (request) => {
484
606
  if (!target)
485
607
  continue;
486
608
  const key = staticPreviewTargetKey(target);
609
+ const count = change.variantRequest?.count;
487
610
  const existing = groups.get(key);
488
611
  if (existing) {
489
612
  existing.instruction = `${existing.instruction}\n\n${change.instruction}`;
613
+ if (count != null) {
614
+ existing.count = Math.max(existing.count ?? 0, count);
615
+ }
616
+ existing.element = existing.element ?? change.element;
490
617
  }
491
618
  else {
492
619
  groups.set(key, {
493
620
  target,
494
621
  instruction: change.instruction,
622
+ ...(count != null ? { count } : {}),
623
+ element: change.element,
624
+ ...(requestId ? { requestId } : {}),
495
625
  });
496
626
  }
497
627
  }
498
628
  return [...groups.values()];
499
629
  };
500
630
  exports.collectStaticPreviewRefineTargetInstructions = collectStaticPreviewRefineTargetInstructions;
501
- const buildStaticPreviewRefineResponse = (consumedRequest) => {
631
+ const buildStaticPreviewRefineResponse = (consumedRequest, droppedRefinements = [], createdWorkItemIds = [], lease) => {
502
632
  if (!consumedRequest) {
503
633
  return buildSupersededChangesResponse();
504
634
  }
@@ -516,7 +646,19 @@ const buildStaticPreviewRefineResponse = (consumedRequest) => {
516
646
  sourceFiles: consumedRequest.sourceFiles,
517
647
  staticPreviewTarget: consumedRequest.staticPreviewTarget,
518
648
  staticPreviewTargets,
519
- applyGuide: STATIC_PREVIEW_REFINE_GUIDE,
649
+ ...(lease ? buildLeasePayloadFields(lease) : {}),
650
+ applyGuide: selectStaticPreviewRefineGuide(targetInstructions),
651
+ // The exact work items THIS batch created. Lease ONLY these (pass them
652
+ // as `workItemIds` to request_work) so concurrent runs each pick up
653
+ // their own batch instead of one greedily leasing everything.
654
+ ...(createdWorkItemIds.length > 0 ? { workItemIds: createdWorkItemIds } : {}),
655
+ // In-place refinements the server could not apply (the variant was
656
+ // already sent / no longer editable). Surfaced structurally so the
657
+ // agent knows the instruction was NOT applied, instead of the poll
658
+ // throwing and losing the already-consumed batch.
659
+ ...(droppedRefinements.length > 0
660
+ ? { rejectedRefinements: droppedRefinements }
661
+ : {}),
520
662
  }),
521
663
  },
522
664
  ...imageContent,
@@ -524,6 +666,23 @@ const buildStaticPreviewRefineResponse = (consumedRequest) => {
524
666
  };
525
667
  };
526
668
  exports.buildStaticPreviewRefineResponse = buildStaticPreviewRefineResponse;
669
+ /**
670
+ * Build the MCP response for a leased variant batch.
671
+ */
672
+ const buildLeasedVariantResponse = (params) => {
673
+ const intent = params.lease.intent;
674
+ if (params.isFreshStaticPreviewVariantRequestBatch(intent)) {
675
+ return params.withPendingVariantRequests(params.handleStaticPreviewRefineBatch(intent, params.lease));
676
+ }
677
+ if ((0, changeBatchClassification_1.isSingleVariantRequestBatch)(intent)) {
678
+ return params.withPendingVariantRequests((0, exports.buildVariantRequestBatchResponse)(intent, params.lease));
679
+ }
680
+ if ((0, changeBatchClassification_1.isStaticPreviewRefineBatch)(intent)) {
681
+ return params.withPendingVariantRequests(params.handleStaticPreviewRefineBatch(intent, params.lease));
682
+ }
683
+ return params.withPendingVariantRequests((0, exports.buildVariantRequestBatchResponse)(intent, params.lease));
684
+ };
685
+ exports.buildLeasedVariantResponse = buildLeasedVariantResponse;
527
686
  /**
528
687
  * Builds the fully configured Rivet MCP server (tools, auth gating, session
529
688
  * bridge, telemetry) without binding it to a transport. Shared by the stdio
@@ -559,6 +718,94 @@ const createRivetMcp = (options) => {
559
718
  bridge,
560
719
  telemetry,
561
720
  });
721
+ /**
722
+ * Resolve a variants sessionId to its projectContext kind, swallowing the
723
+ * `SESSION_NOT_FOUND` throw for unknown/expired sessions so classification can
724
+ * fall back to the default path.
725
+ */
726
+ const resolveSessionKind = (sessionId) => {
727
+ try {
728
+ return agentVariantsOrchestrator.getProjectContext(sessionId).kind;
729
+ }
730
+ catch {
731
+ return undefined;
732
+ }
733
+ };
734
+ /**
735
+ * A `variant_request` ("generate N variants") on a FRESH static-preview
736
+ * variant has no file-backed worktree to code_gen against: the inline preview
737
+ * HTML is gitignored and never lands in a `git worktree` clone, so the default
738
+ * `mode:"existing"` guide would send the agent to edit files that don't exist.
739
+ * Reroute it through the static-preview refine flow, which operates on the
740
+ * variant's inline HTML.
741
+ */
742
+ const isFreshStaticPreviewVariantRequestBatch = (intent) => (0, changeBatchClassification_1.isFreshStaticPreviewVariantRequest)(intent, resolveSessionKind, (sid, vid) => agentVariantsOrchestrator.isStaticPreviewVariant(sid, vid));
743
+ /**
744
+ * Kick off static-preview refinement work items for a leased batch.
745
+ */
746
+ const handleStaticPreviewRefineBatch = (intent, lease) => {
747
+ const consumedRequest = (0, changeBatchClassification_1.withDerivedStaticPreviewTargets)(intent);
748
+ let droppedRefinements = [];
749
+ let createdWorkItemIds = [];
750
+ if (consumedRequest) {
751
+ const targetInstructions = (0, exports.collectStaticPreviewRefineTargetInstructions)(consumedRequest);
752
+ const sessionId = targetInstructions[0]?.target.sessionId;
753
+ if (sessionId) {
754
+ ({ droppedRefinements, createdWorkItemIds } =
755
+ agentVariantsOrchestrator.beginStaticPreviewRefinements({
756
+ sessionId,
757
+ targets: targetInstructions.map(({ target, instruction, count, element, requestId }) => ({
758
+ variantId: target.variantId,
759
+ instruction,
760
+ count,
761
+ element,
762
+ requestId,
763
+ })),
764
+ }));
765
+ }
766
+ }
767
+ return (0, exports.buildStaticPreviewRefineResponse)(consumedRequest, droppedRefinements, createdWorkItemIds, lease);
768
+ };
769
+ /** Complete an apply lease and refresh git status in the Rivet UI. */
770
+ const handleApplyAck = (sessionId, args) => {
771
+ const leaseId = args.leaseId ?? bridge.findActiveApplyLease()?.leaseId ?? undefined;
772
+ if (leaseId) {
773
+ const ackResult = bridge.ackBatch({
774
+ leaseId,
775
+ requestId: args.requestId,
776
+ });
777
+ if (ackResult.kind === 'apply' && ackResult.mergedRequestIds.length > 0) {
778
+ telemetry.trackMCPChangeAcked({
779
+ channel: 'apply',
780
+ leaseId,
781
+ requestId: args.requestId ?? ackResult.mergedRequestIds[0],
782
+ changeCount: ackResult.mergedRequestIds.length,
783
+ sessionId,
784
+ });
785
+ }
786
+ }
787
+ handleRefreshGit(sessionId);
788
+ bridge.setStatus('READY');
789
+ };
790
+ /** Release a variant lease after the agent finishes the batch. */
791
+ const handleVariantAck = (args) => {
792
+ if (!args.leaseId) {
793
+ return;
794
+ }
795
+ const ackResult = bridge.ackBatch({
796
+ leaseId: args.leaseId,
797
+ requestId: args.requestId,
798
+ });
799
+ if (ackResult.kind === 'variant' && ackResult.mergedRequestIds.length > 0) {
800
+ telemetry.trackMCPChangeAcked({
801
+ channel: 'variant',
802
+ leaseId: args.leaseId,
803
+ requestId: args.requestId ?? ackResult.mergedRequestIds[0],
804
+ changeCount: ackResult.mergedRequestIds.length,
805
+ sessionId: bridge.getSessionId() ?? undefined,
806
+ });
807
+ }
808
+ };
562
809
  /** Complete the active run correlation and refresh git status in the Rivet UI. */
563
810
  const handleRefreshGit = (sessionId) => {
564
811
  const activeRunCorrelation = bridge.completeActiveRunCorrelation();
@@ -575,12 +822,11 @@ const createRivetMcp = (options) => {
575
822
  bridge.reportFeedback('__RIVET_REFRESH_GIT__');
576
823
  };
577
824
  /**
578
- * Start tracking an MCP apply run after intent pickup.
825
+ * Start tracking an MCP apply run after a batch is leased.
579
826
  */
580
- const beginApplyingRun = (intent, sessionId) => {
827
+ const beginApplyingRun = (intent, sessionId, _lease) => {
581
828
  bridge.setStatus('APPLYING');
582
829
  const { newRun, orphanedRun } = bridge.startApplyingRun(intent);
583
- bridge.clearPending();
584
830
  trackChangesQueuedFromIntent(intent, bridge, telemetry, sessionId);
585
831
  if (orphanedRun) {
586
832
  trackOrphanedMCPRun(telemetry, sessionId, orphanedRun);
@@ -595,6 +841,66 @@ const createRivetMcp = (options) => {
595
841
  source: 'mcp',
596
842
  });
597
843
  };
844
+ /**
845
+ * Lease and route the next batch from the unified bridge pipeline.
846
+ */
847
+ const leaseAndRouteBatch = (sessionId, requestId) => {
848
+ while (true) {
849
+ const applyHead = bridge.getPendingChanges();
850
+ if (!applyHead || !(0, changeBatchClassification_1.isMixedVariantBatch)(applyHead)) {
851
+ break;
852
+ }
853
+ const rejectedRequest = bridge.rejectMixedVariantRequestBatch(applyHead);
854
+ return withPendingVariantRequests((0, exports.buildMixedVariantBatchResponse)(rejectedRequest));
855
+ }
856
+ const lease = bridge.leaseBatch(requestId);
857
+ if (!lease) {
858
+ return null;
859
+ }
860
+ telemetry.trackMCPChangeLeased({
861
+ channel: lease.kind,
862
+ leaseId: lease.leaseId,
863
+ requestId: lease.requestId,
864
+ changeCount: lease.mergedFrom,
865
+ sessionId,
866
+ });
867
+ if (lease.kind === 'variant') {
868
+ return (0, exports.buildLeasedVariantResponse)({
869
+ lease,
870
+ isFreshStaticPreviewVariantRequestBatch,
871
+ handleStaticPreviewRefineBatch,
872
+ withPendingVariantRequests,
873
+ });
874
+ }
875
+ beginApplyingRun(lease.intent, sessionId, lease);
876
+ return (0, exports.buildApplyChangesResponse)({
877
+ intent: lease.intent,
878
+ lease,
879
+ pendingVariantRequests: bridge.pendingVariantRequestCount(),
880
+ });
881
+ };
882
+ /**
883
+ * Stamp the live variant-request FIFO depth onto a built change response's
884
+ * first text block as `pendingVariantRequests`. Read from the bridge AFTER
885
+ * any batch was consumed, so the count reflects how many MORE batches are
886
+ * still queued (`> 0` ⇒ the agent should keep draining via
887
+ * watch_for_changes / get_pending_changes). Non-JSON text blocks pass through
888
+ * unchanged. Mutates and returns the same response object for convenience.
889
+ */
890
+ const withPendingVariantRequests = (response) => {
891
+ const head = response.content[0];
892
+ if (head?.type !== 'text')
893
+ return response;
894
+ try {
895
+ const parsed = JSON.parse(head.text);
896
+ parsed.pendingVariantRequests = bridge.pendingVariantRequestCount();
897
+ head.text = JSON.stringify(parsed);
898
+ }
899
+ catch {
900
+ // Not a JSON payload — leave it untouched.
901
+ }
902
+ return response;
903
+ };
598
904
  // Tailor the sign-in instructions to the user's stored credentials so the
599
905
  // agent is told to sign in up front (rather than after an AUTH_REQUIRED
600
906
  // failure). A synchronous disk read is enough: the presence of an access or
@@ -892,7 +1198,7 @@ const createRivetMcp = (options) => {
892
1198
  .string()
893
1199
  .describe('The session ID returned by open_visual_editor'),
894
1200
  };
895
- guardedMcp.tool('open_visual_editor', 'Start a Rivet visual editing session. Pass projectPath directly; framework is optional and will be detected when omitted. If the response has opened: false, follow the nextStep/nextAction instructions to recover. Once opened, follow the next_step instructions exactly.', openEditorArgs, async (args) => {
1201
+ guardedMcp.tool('open_visual_editor', 'Start a Rivet visual editing session. Pass projectPath directly; framework is optional and will be detected when omitted. If the response has opened: false, follow the nextStep/nextAction instructions to recover. If opened: true, the response includes nextAction { tool: "watch_for_changes", ... }; host agents must call it immediately and keep that loop active until the user stops or closes the editor.', openEditorArgs, async (args) => {
896
1202
  if (bridge.isActive()) {
897
1203
  return {
898
1204
  content: [
@@ -1398,26 +1704,14 @@ const createRivetMcp = (options) => {
1398
1704
  startupModeSelection,
1399
1705
  requestedStartupMode,
1400
1706
  isGitEnabled: isGitEnabledForSession,
1401
- next_step: mcpEditor === 'cursor' ||
1402
- mcpEditor === 'codex' ||
1403
- mcpEditor === 'claude-desktop'
1404
- ? `Rivet is open. Tell the user: "Rivet is ready — make your visual changes and click '${sendButtonLabel}'." Then call watch_for_changes({ sessionId: "${sessionId}" }) to wait for their changes. After applying, STOP and ask the user if they want to keep watching or go back to normal prompting.`
1405
- : `Rivet is open. Tell the user: "Rivet is ready — make your visual changes and click '${sendButtonLabel}'."
1406
-
1407
- IMPORTANT — Start a background monitor so you automatically detect when the user sends changes. Use the Monitor tool:
1408
- description: "Rivet changes"
1409
- persistent: true
1410
- command: while sleep 2; do if curl -sf http://localhost:${rivetPort}/api/mcp/status | grep -q '"hasPendingIntent":true'; then echo "Rivet: user sent changes - call get_pending_changes to apply"; exit 0; fi; done
1411
-
1412
- When the monitor notifies you, call get_pending_changes({ sessionId: "${sessionId}" }) to retrieve the changes and apply them. After applying, restart the monitor to watch for the next round. You are also free to handle other requests while the monitor runs.
1413
-
1414
- If the Monitor tool is not available, call watch_for_changes({ sessionId: "${sessionId}" }) instead to block until the user sends changes.`,
1707
+ nextAction: (0, exports.buildVisualEditorWatchAction)(sessionId),
1708
+ next_step: `Rivet is open. Tell the user: "Rivet is ready — make your visual changes and click '${sendButtonLabel}'." Then immediately call watch_for_changes({ sessionId: "${sessionId}" }) and keep the watch loop active until the user stops or closes the editor. If watch_for_changes times out, call it again. After handling a batch, call it again; for hosts with background workers, dispatch static-preview variant/refine batches and re-watch immediately, and for hosts without background workers, process the scoped batch inline before re-watching.`,
1415
1709
  }),
1416
1710
  },
1417
1711
  ],
1418
1712
  };
1419
1713
  });
1420
- guardedMcp.tool('watch_for_changes', `Block until the user clicks "${sendButtonLabel}" in Rivet, then return their changes. If it times out (hasChanges: false), call it again to keep waiting. When hasChanges: true, apply the changes, then STOP and ask the user: "Want me to keep watching for more Rivet changes, or go back to normal prompting?"`, {
1714
+ guardedMcp.tool('watch_for_changes', `Required visual-editor watch loop. Call immediately after a successful open_visual_editor response, or after a start_variants response that includes editorNextAction/visualEditor. It blocks until the user sends a change batch, then returns a leased payload with leaseId and kind. After editing files (apply) call watch_for_changes again with refresh_git: true and the leaseId to release the lease and refresh git. After variant batches call watch_for_changes with ack_batch: true and the leaseId. If it times out (hasChanges: false), call it again to keep waiting.`, {
1421
1715
  sessionId: v3_1.z
1422
1716
  .string()
1423
1717
  .describe('The session ID returned by open_visual_editor'),
@@ -1434,7 +1728,19 @@ If the Monitor tool is not available, call watch_for_changes({ sessionId: "${ses
1434
1728
  refresh_git: v3_1.z
1435
1729
  .boolean()
1436
1730
  .optional()
1437
- .describe('Set to true after writing files to trigger a git status refresh in the Rivet UI'),
1731
+ .describe('After completing an apply lease: set true with leaseId to ack the lease and trigger a git status refresh in the Rivet UI'),
1732
+ ack_batch: v3_1.z
1733
+ .boolean()
1734
+ .optional()
1735
+ .describe('After completing a variant lease: set true with leaseId to release the leased batch'),
1736
+ leaseId: v3_1.z
1737
+ .string()
1738
+ .optional()
1739
+ .describe('The leaseId from the batch you just completed'),
1740
+ requestId: v3_1.z
1741
+ .string()
1742
+ .optional()
1743
+ .describe('Optional requestId from the leased batch for idempotent ack'),
1438
1744
  }, async (args) => {
1439
1745
  if (!bridge.isActive() || bridge.getSessionId() !== args.sessionId) {
1440
1746
  return {
@@ -1452,75 +1758,46 @@ If the Monitor tool is not available, call watch_for_changes({ sessionId: "${ses
1452
1758
  bridge.reportFeedback(args.message);
1453
1759
  }
1454
1760
  if (args.refresh_git) {
1455
- handleRefreshGit(args.sessionId);
1761
+ handleApplyAck(args.sessionId, {
1762
+ leaseId: args.leaseId,
1763
+ requestId: args.requestId,
1764
+ });
1765
+ }
1766
+ if (args.ack_batch) {
1767
+ handleVariantAck({
1768
+ leaseId: args.leaseId,
1769
+ requestId: args.requestId,
1770
+ });
1771
+ }
1772
+ // `handleApplyAck` sets READY so the UI can clear comment markers. Do not
1773
+ // overwrite it synchronously — the UI polls `/api/mcp/status` and needs
1774
+ // one READY observation before the watch loop re-arms.
1775
+ if (!args.refresh_git) {
1776
+ bridge.setStatus('WATCHING');
1456
1777
  }
1457
- bridge.setStatus('READY');
1458
- let intent;
1459
1778
  try {
1460
- intent = await bridge.waitForChanges(timeoutMs);
1779
+ await bridge.waitForChanges(timeoutMs);
1461
1780
  }
1462
1781
  catch {
1463
1782
  const health = bridge.getDevServerHealth();
1464
- return {
1465
- content: [
1466
- {
1467
- type: 'text',
1468
- text: JSON.stringify({
1469
- hasChanges: false,
1470
- message: 'Timed out waiting for changes. Call watch_for_changes again to keep waiting.',
1471
- devServerHealth: health,
1472
- }),
1473
- },
1474
- ],
1475
- };
1476
- }
1477
- if ((0, changeBatchClassification_1.isMixedVariantBatch)(intent)) {
1478
- const rejectedRequest = bridge.rejectMixedVariantRequestBatch(intent);
1479
- return (0, exports.buildMixedVariantBatchResponse)(rejectedRequest);
1783
+ return (0, exports.buildNoPendingChangesResponse)({
1784
+ message: 'Timed out waiting for changes. Call watch_for_changes again to keep waiting.',
1785
+ devServerHealth: health,
1786
+ pendingVariantRequests: bridge.pendingVariantRequestCount(),
1787
+ });
1480
1788
  }
1481
- if ((0, changeBatchClassification_1.isSingleVariantRequestBatch)(intent)) {
1482
- const consumedRequest = bridge.consumeVariantRequestBatch(intent);
1483
- return (0, exports.buildVariantRequestBatchResponse)(consumedRequest);
1789
+ const routed = leaseAndRouteBatch(args.sessionId, (0, exports.resolveLeasePickupRequestId)(args));
1790
+ if (routed) {
1791
+ return routed;
1484
1792
  }
1485
- if ((0, changeBatchClassification_1.isStaticPreviewRefineBatch)(intent)) {
1486
- const consumedRequest = bridge.consumeStaticPreviewRefineBatch(intent);
1487
- if (consumedRequest) {
1488
- const targetInstructions = (0, exports.collectStaticPreviewRefineTargetInstructions)(consumedRequest);
1489
- const sessionId = targetInstructions[0]?.target.sessionId;
1490
- if (sessionId) {
1491
- agentVariantsOrchestrator.beginStaticPreviewRefinements({
1492
- sessionId,
1493
- targets: targetInstructions.map(({ target, instruction }) => ({
1494
- variantId: target.variantId,
1495
- instruction,
1496
- })),
1497
- });
1498
- }
1499
- }
1500
- return (0, exports.buildStaticPreviewRefineResponse)(consumedRequest);
1501
- }
1502
- beginApplyingRun(intent, args.sessionId);
1503
- const { changes, imageContent } = (0, changeBatchClassification_1.extractImages)(intent.changes);
1504
- return {
1505
- content: [
1506
- {
1507
- type: 'text',
1508
- text: JSON.stringify({
1509
- hasChanges: true,
1510
- changes,
1511
- sourceFiles: intent.sourceFiles,
1512
- ...(intent.target ? { target: intent.target } : {}),
1513
- ...(intent.worktreePath
1514
- ? { worktreePath: intent.worktreePath }
1515
- : {}),
1516
- applyGuide: buildApplyGuide(intent.worktreePath, requestHasResolvedReferences(intent)),
1517
- }),
1518
- },
1519
- ...imageContent,
1520
- ],
1521
- };
1793
+ const health = bridge.getDevServerHealth();
1794
+ return (0, exports.buildNoPendingChangesResponse)({
1795
+ message: 'No pending changes. Call watch_for_changes again to keep waiting.',
1796
+ devServerHealth: health,
1797
+ pendingVariantRequests: bridge.pendingVariantRequestCount(),
1798
+ });
1522
1799
  });
1523
- guardedMcp.tool('get_pending_changes', `Pick up pending design changes from the Rivet visual editor. Call this when the user asks you to apply Rivet changes (e.g. "apply", "check rivet", "apply changes"). Returns hasChanges: false if no changes are queued yet. Also use this to report feedback to the Rivet UI: pass "message" to share your plan or summarize what you changed, and set refresh_git: true after writing files so the UI reflects the updated git status.`, {
1800
+ guardedMcp.tool('get_pending_changes', `Pick up pending design changes from the Rivet visual editor. Returns a leased batch with leaseId and kind when work is queued. After editing files (apply), call again with refresh_git: true and leaseId to ack and refresh git. After variant batches, call with ack_batch: true and leaseId. Returns hasChanges: false if no changes are queued yet.`, {
1524
1801
  sessionId: v3_1.z
1525
1802
  .string()
1526
1803
  .describe('The session ID returned by open_visual_editor'),
@@ -1531,7 +1808,19 @@ If the Monitor tool is not available, call watch_for_changes({ sessionId: "${ses
1531
1808
  refresh_git: v3_1.z
1532
1809
  .boolean()
1533
1810
  .optional()
1534
- .describe('Set to true after writing files to trigger a git status refresh in the Rivet UI'),
1811
+ .describe('After completing an apply lease: set true with leaseId to ack and trigger a git status refresh in the Rivet UI'),
1812
+ ack_batch: v3_1.z
1813
+ .boolean()
1814
+ .optional()
1815
+ .describe('After completing a variant lease: set true with leaseId to release the leased batch'),
1816
+ leaseId: v3_1.z
1817
+ .string()
1818
+ .optional()
1819
+ .describe('The leaseId from the batch you just completed'),
1820
+ requestId: v3_1.z
1821
+ .string()
1822
+ .optional()
1823
+ .describe('Optional requestId from the leased batch for idempotent ack'),
1535
1824
  }, async (args) => {
1536
1825
  if (!bridge.isActive() || bridge.getSessionId() !== args.sessionId) {
1537
1826
  return {
@@ -1548,66 +1837,24 @@ If the Monitor tool is not available, call watch_for_changes({ sessionId: "${ses
1548
1837
  bridge.reportFeedback(args.message);
1549
1838
  }
1550
1839
  if (args.refresh_git) {
1551
- handleRefreshGit(args.sessionId);
1552
- // Signal the Rivet UI that the round is complete
1553
- bridge.setStatus('READY');
1554
- }
1555
- const intent = bridge.getPendingChanges();
1556
- if (!intent) {
1557
- return {
1558
- content: [
1559
- {
1560
- type: 'text',
1561
- text: JSON.stringify({ hasChanges: false }),
1562
- },
1563
- ],
1564
- };
1565
- }
1566
- if ((0, changeBatchClassification_1.isMixedVariantBatch)(intent)) {
1567
- const rejectedRequest = bridge.rejectMixedVariantRequestBatch(intent);
1568
- return (0, exports.buildMixedVariantBatchResponse)(rejectedRequest);
1840
+ handleApplyAck(args.sessionId, {
1841
+ leaseId: args.leaseId,
1842
+ requestId: args.requestId,
1843
+ });
1569
1844
  }
1570
- if ((0, changeBatchClassification_1.isSingleVariantRequestBatch)(intent)) {
1571
- const consumedRequest = bridge.consumeVariantRequestBatch(intent);
1572
- return (0, exports.buildVariantRequestBatchResponse)(consumedRequest);
1845
+ if (args.ack_batch) {
1846
+ handleVariantAck({
1847
+ leaseId: args.leaseId,
1848
+ requestId: args.requestId,
1849
+ });
1573
1850
  }
1574
- if ((0, changeBatchClassification_1.isStaticPreviewRefineBatch)(intent)) {
1575
- const consumedRequest = bridge.consumeStaticPreviewRefineBatch(intent);
1576
- if (consumedRequest) {
1577
- const targetInstructions = (0, exports.collectStaticPreviewRefineTargetInstructions)(consumedRequest);
1578
- const sessionId = targetInstructions[0]?.target.sessionId;
1579
- if (sessionId) {
1580
- agentVariantsOrchestrator.beginStaticPreviewRefinements({
1581
- sessionId,
1582
- targets: targetInstructions.map(({ target, instruction }) => ({
1583
- variantId: target.variantId,
1584
- instruction,
1585
- })),
1586
- });
1587
- }
1588
- }
1589
- return (0, exports.buildStaticPreviewRefineResponse)(consumedRequest);
1851
+ const routed = leaseAndRouteBatch(args.sessionId, (0, exports.resolveLeasePickupRequestId)(args));
1852
+ if (routed) {
1853
+ return routed;
1590
1854
  }
1591
- beginApplyingRun(intent, args.sessionId);
1592
- const { changes, imageContent } = (0, changeBatchClassification_1.extractImages)(intent.changes);
1593
- return {
1594
- content: [
1595
- {
1596
- type: 'text',
1597
- text: JSON.stringify({
1598
- hasChanges: true,
1599
- changes,
1600
- sourceFiles: intent.sourceFiles,
1601
- ...(intent.target ? { target: intent.target } : {}),
1602
- ...(intent.worktreePath
1603
- ? { worktreePath: intent.worktreePath }
1604
- : {}),
1605
- applyGuide: buildApplyGuide(intent.worktreePath, requestHasResolvedReferences(intent)),
1606
- }),
1607
- },
1608
- ...imageContent,
1609
- ],
1610
- };
1855
+ return (0, exports.buildNoPendingChangesResponse)({
1856
+ pendingVariantRequests: bridge.pendingVariantRequestCount(),
1857
+ });
1611
1858
  });
1612
1859
  guardedMcp.tool('close_visual_editor', 'Stop the Rivet visual editing session and clean up resources. Call this when the user is done making visual changes.', sessionIdArgs, async (args) => {
1613
1860
  if (!bridge.isActive() || bridge.getSessionId() !== args.sessionId) {
@@ -1643,6 +1890,7 @@ If the Monitor tool is not available, call watch_for_changes({ sessionId: "${ses
1643
1890
  leaseOwner: () => 'mcp-server',
1644
1891
  ensureVisualEditorOpen: ensureZeroToOneVisualEditorOpen,
1645
1892
  getProjectPath: () => bridge.getProjectPath(),
1893
+ pendingVariantRequestCount: () => bridge.pendingVariantRequestCount(),
1646
1894
  isVariantSendEnabled: async () => {
1647
1895
  const flags = await (0, evaluateFlags_1.getFeatureFlags)();
1648
1896
  return flags.variantSend.isEnabled;