rivet-design 0.11.16 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +5 -3
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +29 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +185 -14
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +13 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/briefContent.d.ts +16 -0
- package/dist/mcp/agent-variants/briefContent.d.ts.map +1 -0
- package/dist/mcp/agent-variants/briefContent.js +42 -0
- package/dist/mcp/agent-variants/briefContent.js.map +1 -0
- package/dist/mcp/agent-variants/contracts.d.ts +1 -0
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +1 -1
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +10 -0
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +11 -4
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/server.d.ts +57 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +457 -48
- package/dist/mcp/server.js.map +1 -1
- package/dist/prototype-host.js +1 -1
- package/dist/routes/agentVariants.d.ts +15 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +173 -7
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/comments.d.ts +3 -0
- package/dist/routes/comments.d.ts.map +1 -0
- package/dist/routes/comments.js +113 -0
- package/dist/routes/comments.js.map +1 -0
- package/dist/routes/prototypeHost.d.ts +1 -1
- package/dist/routes/prototypeHost.d.ts.map +1 -1
- package/dist/routes/prototypeHost.js +164 -12
- package/dist/routes/prototypeHost.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/services/BundleStore.d.ts +14 -0
- package/dist/services/BundleStore.d.ts.map +1 -1
- package/dist/services/BundleStore.js +77 -13
- package/dist/services/BundleStore.js.map +1 -1
- package/dist/services/HistoryReplayService.d.ts +78 -0
- package/dist/services/HistoryReplayService.d.ts.map +1 -0
- package/dist/services/HistoryReplayService.js +265 -0
- package/dist/services/HistoryReplayService.js.map +1 -0
- package/dist/services/ProjectDetectionService.d.ts.map +1 -1
- package/dist/services/ProjectDetectionService.js +14 -5
- package/dist/services/ProjectDetectionService.js.map +1 -1
- package/dist/services/PrototypeBuildService.d.ts +2 -2
- package/dist/services/PrototypeBuildService.js +2 -2
- package/dist/services/PrototypeDeployService.d.ts +11 -3
- package/dist/services/PrototypeDeployService.d.ts.map +1 -1
- package/dist/services/PrototypeDeployService.js +22 -9
- package/dist/services/PrototypeDeployService.js.map +1 -1
- package/dist/services/TreeStore.d.ts +118 -0
- package/dist/services/TreeStore.d.ts.map +1 -0
- package/dist/services/TreeStore.js +386 -0
- package/dist/services/TreeStore.js.map +1 -0
- package/dist/services/VariantHistoryService.d.ts +83 -0
- package/dist/services/VariantHistoryService.d.ts.map +1 -1
- package/dist/services/VariantHistoryService.js +169 -30
- package/dist/services/VariantHistoryService.js.map +1 -1
- package/dist/utils/skills/claude-skill.d.ts +1 -1
- package/dist/utils/skills/claude-skill.js +1 -1
- package/dist/utils/skills/cursor-rules.d.ts +1 -1
- package/dist/utils/skills/cursor-rules.js +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts +2 -2
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +8 -6
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-D5T0vjqq.js +655 -0
- package/src/ui/dist/assets/main-D_9NxkWD.css +1 -0
- package/src/ui/dist/favicon-16.png +0 -0
- package/src/ui/dist/favicon-32.png +0 -0
- package/src/ui/dist/favicon.ico +0 -0
- package/src/ui/dist/index.html +4 -2
- package/src/ui/dist/assets/main-RWAqAMXv.css +0 -1
- package/src/ui/dist/assets/main-X83y2cfF.js +0 -655
package/dist/mcp/server.js
CHANGED
|
@@ -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.ackVariantBridgeLease = exports.ackApplyBridgeLease = exports.createVariantRequestLeaseTracker = exports.buildLeasedVariantResponse = exports.buildStaticPreviewRefineResponse = exports.collectStaticPreviewRefineTargetInstructions = exports.buildApplyChangesResponse = exports.buildNoPendingChangesResponse = exports.leaseAvailableBatchForPoll = exports.resolveLeasePickupRequestId = exports.buildVariantRequestBatchResponse = exports.buildMixedVariantBatchResponse = exports.startParentProcessMonitor = exports.buildVisualEditorWatchAction = exports.configureMcpStdioLogging = void 0;
|
|
6
|
+
exports.startMCPServer = exports.createRivetMcp = exports.canReuseExistingProjectVisualEditor = exports.closeDetachedVisualEditorServer = exports.getActiveVisualEditorProjectAction = exports.buildVariantAckFailedResponse = exports.buildVariantManualAckBlockedResponse = exports.ackVariantBridgeLease = exports.ackApplyBridgeLease = exports.createVariantRequestLeaseTracker = exports.buildLeasedVariantResponse = exports.buildStaticPreviewRefineResponse = exports.collectStaticPreviewRefineTargetInstructions = exports.buildApplyChangesResponse = exports.buildNoPendingChangesResponse = exports.leaseAvailableBatchForPoll = 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");
|
|
@@ -720,6 +720,39 @@ const variantRequestWorkKey = (sessionId, workItemId) => `${sessionId}:${workIte
|
|
|
720
720
|
const createVariantRequestLeaseTracker = (params) => {
|
|
721
721
|
const trackedByWorkItem = new Map();
|
|
722
722
|
const trackedByRequest = new Map();
|
|
723
|
+
const finishedVariantWorkByRequest = new Map();
|
|
724
|
+
const markGeneratedWorkFinished = (lease) => {
|
|
725
|
+
finishedVariantWorkByRequest.set(lease.requestId, lease.leaseId);
|
|
726
|
+
};
|
|
727
|
+
const hasFinishedGeneratedWork = (lease) => finishedVariantWorkByRequest.get(lease.requestId) === lease.leaseId;
|
|
728
|
+
const findTrackedLease = (args) => {
|
|
729
|
+
if (args.requestId) {
|
|
730
|
+
const tracked = trackedByRequest.get(args.requestId);
|
|
731
|
+
if (tracked) {
|
|
732
|
+
return tracked;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (args.leaseId) {
|
|
736
|
+
for (const tracked of trackedByRequest.values()) {
|
|
737
|
+
if (tracked.leaseId === args.leaseId) {
|
|
738
|
+
return tracked;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return null;
|
|
743
|
+
};
|
|
744
|
+
const findActiveVariantLease = (args) => {
|
|
745
|
+
const activeLeases = params.activeVariantLeases?.() ?? [];
|
|
746
|
+
return (activeLeases.find((lease) => {
|
|
747
|
+
if (lease.kind !== 'variant') {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
if (args.leaseId && lease.leaseId === args.leaseId) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
return Boolean(args.requestId && lease.requestId === args.requestId);
|
|
754
|
+
}) ?? null);
|
|
755
|
+
};
|
|
723
756
|
return {
|
|
724
757
|
claim(args) {
|
|
725
758
|
if (!args.requestId) {
|
|
@@ -733,12 +766,14 @@ const createVariantRequestLeaseTracker = (params) => {
|
|
|
733
766
|
if (trackedByRequest.has(lease.requestId)) {
|
|
734
767
|
return;
|
|
735
768
|
}
|
|
769
|
+
markGeneratedWorkFinished(lease);
|
|
736
770
|
params.ackRequest({
|
|
737
771
|
leaseId: lease.leaseId,
|
|
738
772
|
requestId: lease.requestId,
|
|
739
773
|
});
|
|
740
774
|
return;
|
|
741
775
|
}
|
|
776
|
+
finishedVariantWorkByRequest.delete(lease.requestId);
|
|
742
777
|
const tracked = trackedByRequest.get(lease.requestId) ??
|
|
743
778
|
{
|
|
744
779
|
leaseId: lease.leaseId,
|
|
@@ -767,11 +802,36 @@ const createVariantRequestLeaseTracker = (params) => {
|
|
|
767
802
|
return;
|
|
768
803
|
}
|
|
769
804
|
trackedByRequest.delete(tracked.requestId);
|
|
805
|
+
markGeneratedWorkFinished(tracked);
|
|
770
806
|
params.ackRequest({
|
|
771
807
|
leaseId: tracked.leaseId,
|
|
772
808
|
requestId: tracked.requestId,
|
|
773
809
|
});
|
|
774
810
|
},
|
|
811
|
+
getManualAckBlock(args) {
|
|
812
|
+
const tracked = findTrackedLease(args);
|
|
813
|
+
if (!tracked || tracked.remainingWorkItemIds.size === 0) {
|
|
814
|
+
const activeLease = findActiveVariantLease(args);
|
|
815
|
+
if (!activeLease) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
if (hasFinishedGeneratedWork(activeLease)) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
reason: 'variant_work_unclaimed',
|
|
823
|
+
leaseId: activeLease.leaseId,
|
|
824
|
+
requestId: activeLease.requestId,
|
|
825
|
+
remainingWorkItemIds: [],
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
reason: 'variant_work_unfinished',
|
|
830
|
+
leaseId: tracked.leaseId,
|
|
831
|
+
requestId: tracked.requestId,
|
|
832
|
+
remainingWorkItemIds: [...tracked.remainingWorkItemIds],
|
|
833
|
+
};
|
|
834
|
+
},
|
|
775
835
|
};
|
|
776
836
|
};
|
|
777
837
|
exports.createVariantRequestLeaseTracker = createVariantRequestLeaseTracker;
|
|
@@ -801,8 +861,13 @@ const ackApplyBridgeLease = (args) => {
|
|
|
801
861
|
};
|
|
802
862
|
exports.ackApplyBridgeLease = ackApplyBridgeLease;
|
|
803
863
|
const ackVariantBridgeLease = (args) => {
|
|
804
|
-
if (!args.leaseId)
|
|
805
|
-
return
|
|
864
|
+
if (!args.leaseId) {
|
|
865
|
+
return {
|
|
866
|
+
ok: false,
|
|
867
|
+
reason: 'missing_lease_id',
|
|
868
|
+
requestId: args.requestId,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
806
871
|
const ackResult = args.bridge.ackBatch({
|
|
807
872
|
leaseId: args.leaseId,
|
|
808
873
|
requestId: args.requestId,
|
|
@@ -816,7 +881,12 @@ const ackVariantBridgeLease = (args) => {
|
|
|
816
881
|
changeCount: ackResult.mergedRequestIds.length,
|
|
817
882
|
sessionId,
|
|
818
883
|
});
|
|
819
|
-
return
|
|
884
|
+
return {
|
|
885
|
+
ok: true,
|
|
886
|
+
leaseId: args.leaseId,
|
|
887
|
+
requestId: args.requestId ?? ackResult.mergedRequestIds[0],
|
|
888
|
+
changeCount: ackResult.mergedRequestIds.length,
|
|
889
|
+
};
|
|
820
890
|
}
|
|
821
891
|
args.telemetry.trackMCPChangeAckFailed({
|
|
822
892
|
channel: 'variant',
|
|
@@ -826,8 +896,82 @@ const ackVariantBridgeLease = (args) => {
|
|
|
826
896
|
sessionId,
|
|
827
897
|
failureReason: 'ack_failed',
|
|
828
898
|
});
|
|
899
|
+
return {
|
|
900
|
+
ok: false,
|
|
901
|
+
reason: 'ack_failed',
|
|
902
|
+
leaseId: args.leaseId,
|
|
903
|
+
requestId: args.requestId,
|
|
904
|
+
};
|
|
829
905
|
};
|
|
830
906
|
exports.ackVariantBridgeLease = ackVariantBridgeLease;
|
|
907
|
+
/** Builds the MCP error returned when generated variant work still owns a lease. */
|
|
908
|
+
const buildVariantManualAckBlockedResponse = (block) => ({
|
|
909
|
+
content: [
|
|
910
|
+
{
|
|
911
|
+
type: 'text',
|
|
912
|
+
text: JSON.stringify({
|
|
913
|
+
error: 'variant_ack_blocked',
|
|
914
|
+
reason: block.reason,
|
|
915
|
+
message: block.reason === 'variant_work_unfinished'
|
|
916
|
+
? 'This variant request still has unfinished generated work. Finish it with report_variant_complete; do not use ack_batch to skip or finish generated variant work.'
|
|
917
|
+
: 'This UI variant request has an active lease, but generated work has not claimed it yet. Process it through start_variants or scoped continue_variants({ action: "request_work", workItemIds }) and finish with report_variant_complete; do not use ack_batch to skip variant work.',
|
|
918
|
+
leaseId: block.leaseId,
|
|
919
|
+
requestId: block.requestId,
|
|
920
|
+
remainingWorkItemIds: block.remainingWorkItemIds,
|
|
921
|
+
}),
|
|
922
|
+
},
|
|
923
|
+
],
|
|
924
|
+
isError: true,
|
|
925
|
+
});
|
|
926
|
+
exports.buildVariantManualAckBlockedResponse = buildVariantManualAckBlockedResponse;
|
|
927
|
+
/** Builds the MCP error returned when ack_batch does not match an active variant lease. */
|
|
928
|
+
const buildVariantAckFailedResponse = (result) => ({
|
|
929
|
+
content: [
|
|
930
|
+
{
|
|
931
|
+
type: 'text',
|
|
932
|
+
text: JSON.stringify({
|
|
933
|
+
error: 'variant_ack_failed',
|
|
934
|
+
reason: result.reason,
|
|
935
|
+
message: result.reason === 'missing_lease_id'
|
|
936
|
+
? 'ack_batch requires the leaseId from the variant batch.'
|
|
937
|
+
: 'No active variant lease matched this ack_batch request.',
|
|
938
|
+
...(result.leaseId ? { leaseId: result.leaseId } : {}),
|
|
939
|
+
...(result.requestId ? { requestId: result.requestId } : {}),
|
|
940
|
+
}),
|
|
941
|
+
},
|
|
942
|
+
],
|
|
943
|
+
isError: true,
|
|
944
|
+
});
|
|
945
|
+
exports.buildVariantAckFailedResponse = buildVariantAckFailedResponse;
|
|
946
|
+
/**
|
|
947
|
+
* Chooses how an active visual editor session should handle a project change.
|
|
948
|
+
*/
|
|
949
|
+
const getActiveVisualEditorProjectAction = (args) => {
|
|
950
|
+
if (args.activeProjectPath === args.requestedProjectPath) {
|
|
951
|
+
return 'attach';
|
|
952
|
+
}
|
|
953
|
+
if (args.devServerOwnership === 'none') {
|
|
954
|
+
return 'rebind';
|
|
955
|
+
}
|
|
956
|
+
return 'restart';
|
|
957
|
+
};
|
|
958
|
+
exports.getActiveVisualEditorProjectAction = getActiveVisualEditorProjectAction;
|
|
959
|
+
/**
|
|
960
|
+
* Closes a Rivet server handle that is no longer attached to a bridge session.
|
|
961
|
+
*/
|
|
962
|
+
const closeDetachedVisualEditorServer = async (args) => {
|
|
963
|
+
if (!args.serverHandle || args.isBridgeActive) {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
await args.cleanup();
|
|
967
|
+
return true;
|
|
968
|
+
};
|
|
969
|
+
exports.closeDetachedVisualEditorServer = closeDetachedVisualEditorServer;
|
|
970
|
+
/**
|
|
971
|
+
* Existing-project variants need a mounted upstream for the Original preview.
|
|
972
|
+
*/
|
|
973
|
+
const canReuseExistingProjectVisualEditor = (args) => Boolean(args.attachment && args.devServerOwnership !== 'none');
|
|
974
|
+
exports.canReuseExistingProjectVisualEditor = canReuseExistingProjectVisualEditor;
|
|
831
975
|
/**
|
|
832
976
|
* Builds the fully configured Rivet MCP server (tools, auth gating, session
|
|
833
977
|
* bridge, telemetry) without binding it to a transport. Shared by the stdio
|
|
@@ -916,6 +1060,13 @@ const createRivetMcp = (options) => {
|
|
|
916
1060
|
}
|
|
917
1061
|
return (0, exports.buildStaticPreviewRefineResponse)(consumedRequest, droppedRefinements, createdWorkItemIds, lease);
|
|
918
1062
|
};
|
|
1063
|
+
/** Release a variant bridge lease after tracked generated work has finished. */
|
|
1064
|
+
const ackGeneratedVariantBridgeLease = (args) => {
|
|
1065
|
+
const result = (0, exports.ackVariantBridgeLease)({ bridge, telemetry, ...args });
|
|
1066
|
+
if (!result.ok) {
|
|
1067
|
+
log.warn(`Failed to release generated variant bridge lease ${args.leaseId}: ${result.reason}`);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
919
1070
|
/** Complete an apply lease and refresh git status in the Rivet UI. */
|
|
920
1071
|
const handleApplyAck = (sessionId, args) => {
|
|
921
1072
|
const leaseId = args.leaseId ?? bridge.findActiveApplyLease()?.leaseId ?? undefined;
|
|
@@ -933,11 +1084,20 @@ const createRivetMcp = (options) => {
|
|
|
933
1084
|
};
|
|
934
1085
|
/** Release a variant lease after the agent finishes the batch. */
|
|
935
1086
|
const handleVariantAck = (args) => {
|
|
936
|
-
|
|
1087
|
+
const manualAckBlock = variantRequestLeaseTracker.getManualAckBlock(args);
|
|
1088
|
+
if (manualAckBlock) {
|
|
1089
|
+
return (0, exports.buildVariantManualAckBlockedResponse)(manualAckBlock);
|
|
1090
|
+
}
|
|
1091
|
+
const result = (0, exports.ackVariantBridgeLease)({ bridge, telemetry, ...args });
|
|
1092
|
+
if (!result.ok) {
|
|
1093
|
+
return (0, exports.buildVariantAckFailedResponse)(result);
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
937
1096
|
};
|
|
938
1097
|
const variantRequestLeaseTracker = (0, exports.createVariantRequestLeaseTracker)({
|
|
939
1098
|
leaseRequest: (requestId) => bridge.leaseBatch(requestId),
|
|
940
|
-
ackRequest:
|
|
1099
|
+
ackRequest: ackGeneratedVariantBridgeLease,
|
|
1100
|
+
activeVariantLeases: () => getActiveLeaseMetadata(bridge),
|
|
941
1101
|
});
|
|
942
1102
|
/** Complete the active run correlation and refresh git status in the Rivet UI. */
|
|
943
1103
|
const handleRefreshGit = (sessionId) => {
|
|
@@ -1178,6 +1338,71 @@ const createRivetMcp = (options) => {
|
|
|
1178
1338
|
await shutdown();
|
|
1179
1339
|
});
|
|
1180
1340
|
}
|
|
1341
|
+
/** Returns an attachment for the current visual editor session, if one exists. */
|
|
1342
|
+
const attachActiveVisualEditor = async (projectPath) => {
|
|
1343
|
+
if (!bridge.isActive()) {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
if (!rivetServerUrl || rivetServerPort === null) {
|
|
1347
|
+
throw new Error('Rivet visual editor session is active but URL metadata is unavailable');
|
|
1348
|
+
}
|
|
1349
|
+
const activeEditorSessionId = bridge.getSessionId();
|
|
1350
|
+
if (!activeEditorSessionId) {
|
|
1351
|
+
throw new Error('Rivet visual editor session is active but has no session ID');
|
|
1352
|
+
}
|
|
1353
|
+
const projectAction = (0, exports.getActiveVisualEditorProjectAction)({
|
|
1354
|
+
activeProjectPath: bridge.getProjectPath(),
|
|
1355
|
+
requestedProjectPath: projectPath,
|
|
1356
|
+
devServerOwnership,
|
|
1357
|
+
});
|
|
1358
|
+
if (projectAction === 'restart') {
|
|
1359
|
+
await cleanupSessionResources({ trackSessionEnd: false });
|
|
1360
|
+
return null;
|
|
1361
|
+
}
|
|
1362
|
+
if (projectAction === 'rebind') {
|
|
1363
|
+
bridge.rebindProjectPath(projectPath);
|
|
1364
|
+
}
|
|
1365
|
+
// Don't spawn a duplicate tab when a Rivet page is already open. The
|
|
1366
|
+
// variants flow re-enters this path on every propose/start call; calling
|
|
1367
|
+
// `open()` again opens a fresh tab in most browsers rather than focusing
|
|
1368
|
+
// the existing one. Only (re)open when no UI client is connected — i.e.
|
|
1369
|
+
// the user closed the page and needs it back.
|
|
1370
|
+
if (!agentVariantsOrchestrator.hasUiClients()) {
|
|
1371
|
+
try {
|
|
1372
|
+
await (0, open_1.default)(rivetServerUrl);
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
log.warn('Failed to focus existing Rivet browser automatically');
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
sessionId: activeEditorSessionId,
|
|
1380
|
+
url: rivetServerUrl,
|
|
1381
|
+
rivetPort: rivetServerPort,
|
|
1382
|
+
opened: false,
|
|
1383
|
+
attached: true,
|
|
1384
|
+
};
|
|
1385
|
+
};
|
|
1386
|
+
/** Reads feature flags for a visual editor session without blocking startup on flag failures. */
|
|
1387
|
+
const resolveSessionGitEnabled = async () => {
|
|
1388
|
+
try {
|
|
1389
|
+
const featureFlags = await (0, evaluateFlags_1.getFeatureFlags)();
|
|
1390
|
+
return featureFlags.gitUi.isEnabled;
|
|
1391
|
+
}
|
|
1392
|
+
catch (error) {
|
|
1393
|
+
log.warn('Failed to evaluate feature flags for MCP session startup; defaulting git to enabled:', error);
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
/** Writes editor-specific guidance once a visual editor session is ready. */
|
|
1398
|
+
const writeEditorGuidance = (projectPath) => {
|
|
1399
|
+
if (mcpEditor === 'cursor') {
|
|
1400
|
+
(0, skillWriter_1.writeCursorRulesIfNeeded)(projectPath);
|
|
1401
|
+
}
|
|
1402
|
+
else if (mcpEditor === 'claude' || mcpEditor === undefined) {
|
|
1403
|
+
(0, skillWriter_1.writeSkillFileIfNeeded)();
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1181
1406
|
/**
|
|
1182
1407
|
* Ensure zero-to-one variant sessions have a Rivet UI shell subscribed to
|
|
1183
1408
|
* the shared variants orchestrator. Fresh projects do not have a base app
|
|
@@ -1188,38 +1413,17 @@ const createRivetMcp = (options) => {
|
|
|
1188
1413
|
// The bridge tracks the generated project folder. That keeps /api/config,
|
|
1189
1414
|
// history reads, and the committed preview focused on the same folder.
|
|
1190
1415
|
const { projectPath } = input;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
// Don't spawn a duplicate tab when a Rivet page is already open. The
|
|
1203
|
-
// variants flow re-enters this path on every propose/start call; calling
|
|
1204
|
-
// `open()` again opens a fresh tab in most browsers rather than focusing
|
|
1205
|
-
// the existing one. Only (re)open when no UI client is connected — i.e.
|
|
1206
|
-
// the user closed the page and needs it back.
|
|
1207
|
-
if (!agentVariantsOrchestrator.hasUiClients()) {
|
|
1208
|
-
try {
|
|
1209
|
-
await (0, open_1.default)(rivetServerUrl);
|
|
1210
|
-
}
|
|
1211
|
-
catch {
|
|
1212
|
-
log.warn('Failed to focus existing Rivet browser automatically');
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
return {
|
|
1216
|
-
sessionId: activeEditorSessionId,
|
|
1217
|
-
url: rivetServerUrl,
|
|
1218
|
-
rivetPort: rivetServerPort,
|
|
1219
|
-
opened: false,
|
|
1220
|
-
attached: true,
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1416
|
+
const activeAttachment = await attachActiveVisualEditor(projectPath);
|
|
1417
|
+
if (activeAttachment) {
|
|
1418
|
+
return activeAttachment;
|
|
1419
|
+
}
|
|
1420
|
+
await (0, exports.closeDetachedVisualEditorServer)({
|
|
1421
|
+
isBridgeActive: bridge.isActive(),
|
|
1422
|
+
serverHandle,
|
|
1423
|
+
cleanup: async () => {
|
|
1424
|
+
await cleanupSessionResources({ trackSessionEnd: false });
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1223
1427
|
let nextRivetPort;
|
|
1224
1428
|
try {
|
|
1225
1429
|
nextRivetPort = await (0, portUtils_1.findAvailablePort)(index_1.DEFAULT_PORT);
|
|
@@ -1282,12 +1486,210 @@ const createRivetMcp = (options) => {
|
|
|
1282
1486
|
// of through the open_visual_editor tool, so without this the
|
|
1283
1487
|
// SKILL.md / rivet.mdc file would never be rewritten when only
|
|
1284
1488
|
// start_variants is used.
|
|
1285
|
-
|
|
1286
|
-
|
|
1489
|
+
writeEditorGuidance(input.projectPath);
|
|
1490
|
+
const url = `http://localhost:${nextRivetPort}/rivet`;
|
|
1491
|
+
rivetServerPort = nextRivetPort;
|
|
1492
|
+
rivetServerUrl = url;
|
|
1493
|
+
try {
|
|
1494
|
+
await (0, open_1.default)(url);
|
|
1287
1495
|
}
|
|
1288
|
-
|
|
1289
|
-
(
|
|
1496
|
+
catch {
|
|
1497
|
+
log.warn('Failed to open browser automatically');
|
|
1290
1498
|
}
|
|
1499
|
+
return {
|
|
1500
|
+
sessionId: editorSessionId,
|
|
1501
|
+
url,
|
|
1502
|
+
rivetPort: nextRivetPort,
|
|
1503
|
+
opened: true,
|
|
1504
|
+
attached: false,
|
|
1505
|
+
};
|
|
1506
|
+
};
|
|
1507
|
+
/**
|
|
1508
|
+
* Ensure existing-project variant sessions have the base app mounted as the
|
|
1509
|
+
* default preview upstream before variant preview targets are selected.
|
|
1510
|
+
*/
|
|
1511
|
+
const ensureExistingVisualEditorOpen = async (input) => {
|
|
1512
|
+
const { projectPath } = input;
|
|
1513
|
+
const activeAttachment = await attachActiveVisualEditor(projectPath);
|
|
1514
|
+
if (activeAttachment &&
|
|
1515
|
+
(0, exports.canReuseExistingProjectVisualEditor)({
|
|
1516
|
+
attachment: activeAttachment,
|
|
1517
|
+
devServerOwnership,
|
|
1518
|
+
})) {
|
|
1519
|
+
return activeAttachment;
|
|
1520
|
+
}
|
|
1521
|
+
if (activeAttachment) {
|
|
1522
|
+
await cleanupSessionResources({ trackSessionEnd: false });
|
|
1523
|
+
}
|
|
1524
|
+
await (0, exports.closeDetachedVisualEditorServer)({
|
|
1525
|
+
isBridgeActive: bridge.isActive(),
|
|
1526
|
+
serverHandle,
|
|
1527
|
+
cleanup: async () => {
|
|
1528
|
+
await cleanupSessionResources({ trackSessionEnd: false });
|
|
1529
|
+
},
|
|
1530
|
+
});
|
|
1531
|
+
const detection = new index_core_1.ProjectDetectionService();
|
|
1532
|
+
let nextDevServerPort = null;
|
|
1533
|
+
let nextDevServerHost = 'localhost';
|
|
1534
|
+
let nextDevServerOwnership = 'none';
|
|
1535
|
+
let staticTarget = null;
|
|
1536
|
+
if (input.framework === 'static') {
|
|
1537
|
+
staticTarget = (0, staticProjectTarget_1.resolveStaticProjectTarget)({ targetPath: projectPath });
|
|
1538
|
+
if ((0, staticProjectTarget_1.needsStaticEntryPath)(staticTarget)) {
|
|
1539
|
+
throw new Error('Static folder has multiple HTML files and no index.html. Open the visual editor with a specific entry path before starting variants.');
|
|
1540
|
+
}
|
|
1541
|
+
const entryFullPath = path_1.default.join(projectPath, staticTarget.entryPath);
|
|
1542
|
+
const didWriteStarter = staticTarget.htmlEntryFiles.length === 0
|
|
1543
|
+
? (0, staticStarter_1.ensureStaticStarterCanvas)(projectPath)
|
|
1544
|
+
: false;
|
|
1545
|
+
const hasStaticEntry = fs_1.default.existsSync(entryFullPath) ||
|
|
1546
|
+
(didWriteStarter && staticTarget.entryPath === 'index.html');
|
|
1547
|
+
if (hasStaticEntry) {
|
|
1548
|
+
nextDevServerHost = '127.0.0.1';
|
|
1549
|
+
nextDevServerPort = await (0, portUtils_1.findAvailablePort)(index_1.DEFAULT_USER_PORT);
|
|
1550
|
+
devServerProcess = await (0, StaticPreviewServer_1.startStaticPreviewServer)({
|
|
1551
|
+
rootPath: projectPath,
|
|
1552
|
+
port: nextDevServerPort,
|
|
1553
|
+
host: nextDevServerHost,
|
|
1554
|
+
});
|
|
1555
|
+
nextDevServerOwnership = 'rivet_owned';
|
|
1556
|
+
devServerProcess.on('exit', (code) => {
|
|
1557
|
+
log.info(`Static preview server exited with code ${code}`);
|
|
1558
|
+
devServerProcess = null;
|
|
1559
|
+
if (bridge.isActive()) {
|
|
1560
|
+
updateBridgeHealth('upstream_unreachable', false);
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
updateBridgeHealth('healthy', true);
|
|
1564
|
+
log.info(`Static preview server ready on ${nextDevServerHost}:${nextDevServerPort}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
const frameworkConfig = devServerCommand_1.FRAMEWORK_DEV_CONFIG[input.framework];
|
|
1569
|
+
const packageManager = await detection.detectPackageManager(projectPath);
|
|
1570
|
+
const preferredPort = detection.readConfiguredPort(projectPath) ?? frameworkConfig.defaultPort;
|
|
1571
|
+
nextDevServerPort = await (0, portUtils_1.findAvailablePort)(preferredPort);
|
|
1572
|
+
const { cmd, args: cmdArgs, env, } = (0, devServerCommand_1.buildDevServerCommand)(input.framework, packageManager, frameworkConfig.devCommand, nextDevServerPort);
|
|
1573
|
+
log.info(`Auto-starting dev server: ${cmd} ${cmdArgs.join(' ')} in ${projectPath}`);
|
|
1574
|
+
devServerProcess = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
1575
|
+
cwd: projectPath,
|
|
1576
|
+
env: { ...process.env, ...env },
|
|
1577
|
+
stdio: 'pipe',
|
|
1578
|
+
detached: false,
|
|
1579
|
+
});
|
|
1580
|
+
nextDevServerOwnership = 'rivet_owned';
|
|
1581
|
+
devServerProcess.on('error', (err) => {
|
|
1582
|
+
log.warn('Dev server process error:', err);
|
|
1583
|
+
});
|
|
1584
|
+
devServerProcess.on('exit', (code) => {
|
|
1585
|
+
log.info(`Dev server exited with code ${code}`);
|
|
1586
|
+
devServerProcess = null;
|
|
1587
|
+
if (bridge.isActive()) {
|
|
1588
|
+
updateBridgeHealth('upstream_unreachable', false);
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
devServerProcess.stdout?.on('data', (data) => {
|
|
1592
|
+
log.debug(`[dev-server] ${data.toString().trim()}`);
|
|
1593
|
+
});
|
|
1594
|
+
devServerProcess.stderr?.on('data', (data) => {
|
|
1595
|
+
log.debug(`[dev-server] ${data.toString().trim()}`);
|
|
1596
|
+
});
|
|
1597
|
+
const ready = await waitForPort(nextDevServerPort, nextDevServerHost, 30_000);
|
|
1598
|
+
if (!ready) {
|
|
1599
|
+
if (devServerProcess) {
|
|
1600
|
+
await stopChildProcess(devServerProcess);
|
|
1601
|
+
}
|
|
1602
|
+
devServerProcess = null;
|
|
1603
|
+
throw new Error(`Dev server did not become ready on port ${nextDevServerPort} within 30s.`);
|
|
1604
|
+
}
|
|
1605
|
+
const startupValidation = await validateProjectServer({
|
|
1606
|
+
host: nextDevServerHost,
|
|
1607
|
+
port: nextDevServerPort,
|
|
1608
|
+
framework: input.framework,
|
|
1609
|
+
projectPath,
|
|
1610
|
+
});
|
|
1611
|
+
if (!startupValidation.valid) {
|
|
1612
|
+
if (devServerProcess) {
|
|
1613
|
+
await stopChildProcess(devServerProcess);
|
|
1614
|
+
}
|
|
1615
|
+
devServerProcess = null;
|
|
1616
|
+
throw new Error(startupValidation.reason);
|
|
1617
|
+
}
|
|
1618
|
+
updateBridgeHealth('healthy', true);
|
|
1619
|
+
log.info(`Dev server ready on ${nextDevServerHost}:${nextDevServerPort}`);
|
|
1620
|
+
}
|
|
1621
|
+
let nextRivetPort;
|
|
1622
|
+
try {
|
|
1623
|
+
nextRivetPort = await (0, portUtils_1.findAvailablePort)(index_1.DEFAULT_PORT);
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
log.warn(`Port discovery failed for existing-project editor, falling back to ${index_1.DEFAULT_PORT}:`, error);
|
|
1627
|
+
nextRivetPort = index_1.DEFAULT_PORT;
|
|
1628
|
+
}
|
|
1629
|
+
let retries = 0;
|
|
1630
|
+
const maxRetries = 5;
|
|
1631
|
+
const isGitEnabledForSession = await resolveSessionGitEnabled();
|
|
1632
|
+
while (!serverHandle && retries < maxRetries) {
|
|
1633
|
+
try {
|
|
1634
|
+
serverHandle = await (0, server_1.startServer)({
|
|
1635
|
+
framework: input.framework,
|
|
1636
|
+
projectPath,
|
|
1637
|
+
staticEntry: staticTarget?.entryPath,
|
|
1638
|
+
userPort: nextDevServerPort ?? 0,
|
|
1639
|
+
userHost: nextDevServerHost,
|
|
1640
|
+
sessionBridge: bridge,
|
|
1641
|
+
agentVariantsOrchestrator,
|
|
1642
|
+
telemetry,
|
|
1643
|
+
mcpEditor,
|
|
1644
|
+
skipProcessHandlers: true,
|
|
1645
|
+
rivetPort: nextRivetPort,
|
|
1646
|
+
isGitEnabled: isGitEnabledForSession,
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
catch (error) {
|
|
1650
|
+
if (error?.code !== 'EADDRINUSE') {
|
|
1651
|
+
if (devServerProcess) {
|
|
1652
|
+
await stopChildProcess(devServerProcess);
|
|
1653
|
+
}
|
|
1654
|
+
devServerProcess = null;
|
|
1655
|
+
throw error;
|
|
1656
|
+
}
|
|
1657
|
+
retries++;
|
|
1658
|
+
nextRivetPort = await (0, portUtils_1.findAvailablePort)(nextRivetPort + 1);
|
|
1659
|
+
log.debug(`Existing-project editor port in use, retrying with port ${nextRivetPort} (attempt ${retries}/${maxRetries})`);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (!serverHandle) {
|
|
1663
|
+
if (devServerProcess) {
|
|
1664
|
+
await stopChildProcess(devServerProcess);
|
|
1665
|
+
}
|
|
1666
|
+
devServerProcess = null;
|
|
1667
|
+
throw new Error(`Could not start Rivet server after ${maxRetries} attempts.`);
|
|
1668
|
+
}
|
|
1669
|
+
const editorSessionId = bridge.startSession(projectPath);
|
|
1670
|
+
sessionStartedAt = Date.now();
|
|
1671
|
+
devServerOwnership = nextDevServerOwnership;
|
|
1672
|
+
devServerHost = nextDevServerHost;
|
|
1673
|
+
devServerPort = nextDevServerPort;
|
|
1674
|
+
bridge.setDevServerContext({
|
|
1675
|
+
ownership: devServerOwnership,
|
|
1676
|
+
host: devServerHost,
|
|
1677
|
+
port: devServerPort,
|
|
1678
|
+
startedByRivet: devServerOwnership === 'rivet_owned',
|
|
1679
|
+
});
|
|
1680
|
+
if (devServerOwnership === 'none') {
|
|
1681
|
+
updateBridgeHealth('unknown', null);
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
updateBridgeHealth('healthy', true);
|
|
1685
|
+
}
|
|
1686
|
+
telemetry.trackMCPSessionStart({
|
|
1687
|
+
framework: input.framework,
|
|
1688
|
+
devServerStarted: devServerOwnership === 'rivet_owned',
|
|
1689
|
+
sessionId: editorSessionId,
|
|
1690
|
+
mcpEditor,
|
|
1691
|
+
});
|
|
1692
|
+
writeEditorGuidance(projectPath);
|
|
1291
1693
|
const url = `http://localhost:${nextRivetPort}/rivet`;
|
|
1292
1694
|
rivetServerPort = nextRivetPort;
|
|
1293
1695
|
rivetServerUrl = url;
|
|
@@ -1854,7 +2256,7 @@ const createRivetMcp = (options) => {
|
|
|
1854
2256
|
],
|
|
1855
2257
|
};
|
|
1856
2258
|
});
|
|
1857
|
-
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.
|
|
2259
|
+
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 apply lease and refresh git. For UI-originated variant_request/static_preview_refine batches, lease the exact workItemIds and finish generated work with report_variant_complete; do not use ack_batch to skip or finish generated variant work. If it times out (hasChanges: false), call it again to keep waiting.`, {
|
|
1858
2260
|
sessionId: v3_1.z
|
|
1859
2261
|
.string()
|
|
1860
2262
|
.describe('The session ID returned by open_visual_editor'),
|
|
@@ -1875,7 +2277,7 @@ const createRivetMcp = (options) => {
|
|
|
1875
2277
|
ack_batch: v3_1.z
|
|
1876
2278
|
.boolean()
|
|
1877
2279
|
.optional()
|
|
1878
|
-
.describe('
|
|
2280
|
+
.describe('Only for variant leases that are not tied to generated work items. UI-originated variant_request/static_preview_refine work is released by report_variant_complete; unclaimed or unfinished generated work rejects ack_batch.'),
|
|
1879
2281
|
leaseId: v3_1.z
|
|
1880
2282
|
.string()
|
|
1881
2283
|
.optional()
|
|
@@ -1907,10 +2309,13 @@ const createRivetMcp = (options) => {
|
|
|
1907
2309
|
});
|
|
1908
2310
|
}
|
|
1909
2311
|
if (args.ack_batch) {
|
|
1910
|
-
handleVariantAck({
|
|
2312
|
+
const ackError = handleVariantAck({
|
|
1911
2313
|
leaseId: args.leaseId,
|
|
1912
2314
|
requestId: args.requestId,
|
|
1913
2315
|
});
|
|
2316
|
+
if (ackError) {
|
|
2317
|
+
return ackError;
|
|
2318
|
+
}
|
|
1914
2319
|
}
|
|
1915
2320
|
// `handleApplyAck` sets READY so the UI can clear comment markers. Do not
|
|
1916
2321
|
// overwrite it synchronously — the UI polls `/api/mcp/status` and needs
|
|
@@ -1942,7 +2347,7 @@ const createRivetMcp = (options) => {
|
|
|
1942
2347
|
activeLeases: getActiveLeaseMetadata(bridge),
|
|
1943
2348
|
});
|
|
1944
2349
|
});
|
|
1945
|
-
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.
|
|
2350
|
+
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. For UI-originated variant_request/static_preview_refine batches, lease the exact workItemIds and finish generated work with report_variant_complete; do not use ack_batch to skip or finish generated variant work. Returns hasChanges: false if no changes are queued yet.`, {
|
|
1946
2351
|
sessionId: v3_1.z
|
|
1947
2352
|
.string()
|
|
1948
2353
|
.describe('The session ID returned by open_visual_editor'),
|
|
@@ -1957,7 +2362,7 @@ const createRivetMcp = (options) => {
|
|
|
1957
2362
|
ack_batch: v3_1.z
|
|
1958
2363
|
.boolean()
|
|
1959
2364
|
.optional()
|
|
1960
|
-
.describe('
|
|
2365
|
+
.describe('Only for variant leases that are not tied to generated work items. UI-originated variant_request/static_preview_refine work is released by report_variant_complete; unclaimed or unfinished generated work rejects ack_batch.'),
|
|
1961
2366
|
leaseId: v3_1.z
|
|
1962
2367
|
.string()
|
|
1963
2368
|
.optional()
|
|
@@ -1988,10 +2393,13 @@ const createRivetMcp = (options) => {
|
|
|
1988
2393
|
});
|
|
1989
2394
|
}
|
|
1990
2395
|
if (args.ack_batch) {
|
|
1991
|
-
handleVariantAck({
|
|
2396
|
+
const ackError = handleVariantAck({
|
|
1992
2397
|
leaseId: args.leaseId,
|
|
1993
2398
|
requestId: args.requestId,
|
|
1994
2399
|
});
|
|
2400
|
+
if (ackError) {
|
|
2401
|
+
return ackError;
|
|
2402
|
+
}
|
|
1995
2403
|
}
|
|
1996
2404
|
const routed = leaseAndRouteBatch(args.sessionId, (0, exports.resolveLeasePickupRequestId)(args));
|
|
1997
2405
|
if (routed) {
|
|
@@ -2035,6 +2443,7 @@ const createRivetMcp = (options) => {
|
|
|
2035
2443
|
orchestrator: agentVariantsOrchestrator,
|
|
2036
2444
|
leaseOwner: () => 'mcp-server',
|
|
2037
2445
|
ensureVisualEditorOpen: ensureZeroToOneVisualEditorOpen,
|
|
2446
|
+
ensureExistingVisualEditorOpen,
|
|
2038
2447
|
getProjectPath: () => bridge.getProjectPath(),
|
|
2039
2448
|
resolveVariantRequestProjectPath: (requestId) => bridge.resolveVariantRequestProjectPath(requestId),
|
|
2040
2449
|
pendingVariantRequestCount: () => bridge.pendingVariantRequestCount(),
|