fix: support Visual Studio 2026 package loading
This commit is contained in:
@@ -7,13 +7,13 @@ A Windows-local integration that lets DeepSeek Harness users type `@`, choose a
|
||||
- `protocol/`: versioned JSON snapshot contract shared by all components.
|
||||
- `dsh-plugin/`: dual-face Cordis package. The Host reads snapshots; the Client registers the `@` source.
|
||||
- `vscode-extension/`: VS Code companion using `window.tabGroups.all`.
|
||||
- `visualstudio-extension/`: Visual Studio 2022 VSIX using `IVsRunningDocumentTable`.
|
||||
- `visualstudio-extension/`: Visual Studio 2022/2026 VSIX using `IVsRunningDocumentTable`.
|
||||
|
||||
The companions publish paths and dirty flags only. They never copy source text. DSH and the model continue to use the normal filesystem tools, so an unsaved IDE buffer is explicitly marked but is not silently substituted for the disk file.
|
||||
|
||||
## Build
|
||||
|
||||
Prerequisites: Node.js 20+, pnpm 10+, .NET SDK/MSBuild with the Visual Studio 2022 extension workload.
|
||||
Prerequisites: Node.js 20+, pnpm 10+, .NET SDK/MSBuild with the Visual Studio extension workload.
|
||||
|
||||
```powershell
|
||||
pnpm install
|
||||
@@ -29,7 +29,7 @@ Build the DSH package, then install its local profile bundle into the Web profil
|
||||
|
||||
```powershell
|
||||
pnpm --filter ./dsh-plugin build
|
||||
dsh plugin --profile web add F:\dsh-plugin-coop-with-vs\dsh-plugin
|
||||
dsh plugin --profile web add -w F:\dsh-plugin-coop-with-vs\dsh-plugin
|
||||
```
|
||||
|
||||
The package's `cordis.patch.yml` inserts its own shared Host/Client row. Restart `dsh web` after the first install. During development, run the DSH package watcher and the Harness `pnpm run dev:web` watcher when Client HMR is required.
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Prophet.CoopWithDsh\SnapshotModels.cs" Link="SnapshotModels.cs" />
|
||||
<Compile Include="..\Prophet.CoopWithDsh\SnapshotPublisher.cs" Link="SnapshotPublisher.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Prophet.CoopWithDsh.Tests
|
||||
@@ -19,5 +20,32 @@ namespace Prophet.CoopWithDsh.Tests
|
||||
Assert.True(snapshot.Documents[0].Dirty);
|
||||
Assert.Equal("2026-01-01T00:00:00.0000000Z", snapshot.UpdatedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PublisherWritesProtocolV1WithoutExternalJsonRuntime()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "coop-with-dsh-test-" + Guid.NewGuid().ToString("N"));
|
||||
try
|
||||
{
|
||||
using (var publisher = new SnapshotPublisher(root))
|
||||
{
|
||||
var snapshot = SnapshotModel.Create(publisher.InstanceId, 42, null, new[] {
|
||||
new OpenDocument { Path = "F:\\Project\\main.cs", Label = "main.cs", Dirty = true },
|
||||
}, new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
publisher.Publish(snapshot);
|
||||
var json = File.ReadAllText(publisher.FilePath);
|
||||
|
||||
Assert.Contains("\"version\":1", json);
|
||||
Assert.Contains("\"ide\":\"visualstudio\"", json);
|
||||
Assert.Contains("\"documents\":[{", json);
|
||||
Assert.Contains("\"dirty\":true", json);
|
||||
Assert.DoesNotContain("\"workspace\"", json);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(root)) Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<AssemblyName>Prophet.CoopWithDsh</AssemblyName>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<GeneratePkgDefFile>true</GeneratePkgDefFile>
|
||||
<UseCodebase>true</UseCodebase>
|
||||
<CreateVsixContainer>true</CreateVsixContainer>
|
||||
<DeployExtension>false</DeployExtension>
|
||||
<IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
|
||||
@@ -17,7 +18,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.31902.203" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.9.3168" PrivateAssets="all" GeneratePathProperty="true" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="source.extension.vsixmanifest" />
|
||||
|
||||
@@ -2,26 +2,28 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Prophet.CoopWithDsh
|
||||
{
|
||||
[DataContract]
|
||||
internal sealed class OpenDocument
|
||||
{
|
||||
[JsonProperty("path")] public string Path { get; set; } = string.Empty;
|
||||
[JsonProperty("label")] public string Label { get; set; } = string.Empty;
|
||||
[JsonProperty("dirty")] public bool Dirty { get; set; }
|
||||
[DataMember(Name = "path", Order = 1)] public string Path { get; set; } = string.Empty;
|
||||
[DataMember(Name = "label", Order = 2)] public string Label { get; set; } = string.Empty;
|
||||
[DataMember(Name = "dirty", Order = 3)] public bool Dirty { get; set; }
|
||||
}
|
||||
|
||||
[DataContract]
|
||||
internal sealed class OpenFileSnapshot
|
||||
{
|
||||
[JsonProperty("version")] public int Version { get; set; } = 1;
|
||||
[JsonProperty("instanceId")] public string InstanceId { get; set; } = string.Empty;
|
||||
[JsonProperty("ide")] public string Ide { get; set; } = "visualstudio";
|
||||
[JsonProperty("processId")] public int ProcessId { get; set; }
|
||||
[JsonProperty("workspace", NullValueHandling = NullValueHandling.Ignore)] public string? Workspace { get; set; }
|
||||
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; } = string.Empty;
|
||||
[JsonProperty("documents")] public IReadOnlyList<OpenDocument> Documents { get; set; } = Array.Empty<OpenDocument>();
|
||||
[DataMember(Name = "version", Order = 1)] public int Version { get; set; } = 1;
|
||||
[DataMember(Name = "instanceId", Order = 2)] public string InstanceId { get; set; } = string.Empty;
|
||||
[DataMember(Name = "ide", Order = 3)] public string Ide { get; set; } = "visualstudio";
|
||||
[DataMember(Name = "processId", Order = 4)] public int ProcessId { get; set; }
|
||||
[DataMember(Name = "workspace", Order = 5, EmitDefaultValue = false)] public string? Workspace { get; set; }
|
||||
[DataMember(Name = "updatedAt", Order = 6)] public string UpdatedAt { get; set; } = string.Empty;
|
||||
[DataMember(Name = "documents", Order = 7)] public OpenDocument[] Documents { get; set; } = Array.Empty<OpenDocument>();
|
||||
}
|
||||
|
||||
internal static class SnapshotModel
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Json;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Prophet.CoopWithDsh
|
||||
{
|
||||
internal sealed class SnapshotPublisher : IDisposable
|
||||
{
|
||||
private static readonly DataContractJsonSerializer Serializer = new DataContractJsonSerializer(typeof(OpenFileSnapshot));
|
||||
private readonly object gate = new object();
|
||||
internal string InstanceId { get; } = Guid.NewGuid().ToString("N");
|
||||
internal string FilePath { get; }
|
||||
@@ -26,7 +27,11 @@ namespace Prophet.CoopWithDsh
|
||||
var directory = Path.GetDirectoryName(FilePath)!;
|
||||
Directory.CreateDirectory(directory);
|
||||
var temporary = FilePath + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
File.WriteAllText(temporary, JsonConvert.SerializeObject(snapshot) + Environment.NewLine, new UTF8Encoding(false));
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
Serializer.WriteObject(stream, snapshot);
|
||||
File.WriteAllText(temporary, Encoding.UTF8.GetString(stream.ToArray()) + Environment.NewLine, new UTF8Encoding(false));
|
||||
}
|
||||
try
|
||||
{
|
||||
if (File.Exists(FilePath)) File.Replace(temporary, FilePath, null);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
|
||||
<Metadata>
|
||||
<Identity Id="Prophet.CoopWithDsh.1e02d547-499b-41ed-a25e-b356a9687e31" Version="0.1.0" Language="en-US" Publisher="Prophet" />
|
||||
<Identity Id="Prophet.CoopWithDsh.1e02d547-499b-41ed-a25e-b356a9687e31" Version="0.1.1" Language="en-US" Publisher="Prophet" />
|
||||
<DisplayName>DSH Open File Bridge</DisplayName>
|
||||
<Description xml:space="preserve">Publishes Visual Studio open file metadata for DeepSeek Harness @ references.</Description>
|
||||
<License>LICENSE.txt</License>
|
||||
</Metadata>
|
||||
<Installation>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0,18.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[17.0,18.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Enterprise" Version="[17.0,18.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0,19.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[17.0,19.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
<InstallationTarget Id="Microsoft.VisualStudio.Enterprise" Version="[17.0,19.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
|
||||
</Installation>
|
||||
<Dependencies />
|
||||
<Prerequisites>
|
||||
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" />
|
||||
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,19.0)" DisplayName="Visual Studio core editor" />
|
||||
</Prerequisites>
|
||||
<Assets>
|
||||
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="Prophet.CoopWithDsh" Path="|Prophet.CoopWithDsh;PkgdefProjectOutputGroup|" />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# DSH Open File Bridge for Visual Studio 2022
|
||||
# DSH Open File Bridge for Visual Studio
|
||||
|
||||
This VSIX is a background-loaded `AsyncPackage`. Each Visual Studio process enumerates its own `IVsRunningDocumentTable`, listens for RDT changes, and publishes one protocol-v1 snapshot. This avoids the ambiguity and integrity-level failures of attaching externally through EnvDTE/ROT.
|
||||
This VSIX supports Visual Studio 2022 (17.x) and Visual Studio 2026 (18.x). It is a background-loaded `AsyncPackage`. Each Visual Studio process enumerates its own `IVsRunningDocumentTable`, listens for RDT changes, and publishes one protocol-v1 snapshot. This avoids the ambiguity and integrity-level failures of attaching externally through EnvDTE/ROT.
|
||||
|
||||
Only absolute, existing local files are published. Dirty state is read through `IVsPersistDocData`; source buffers are never copied.
|
||||
|
||||
## Build and test
|
||||
|
||||
Use a Visual Studio Developer PowerShell or a machine with the VS 2022 extension workload:
|
||||
Use a Visual Studio Developer PowerShell or a machine with the Visual Studio extension workload:
|
||||
|
||||
```powershell
|
||||
dotnet restore CoopWithDsh.sln
|
||||
|
||||
Reference in New Issue
Block a user