fix: support Visual Studio 2026 package loading

This commit is contained in:
2026-08-17 12:11:27 +08:00
parent d96c11ae3d
commit 97a2c61306
8 changed files with 61 additions and 26 deletions
+3 -3
View File
@@ -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. - `protocol/`: versioned JSON snapshot contract shared by all components.
- `dsh-plugin/`: dual-face Cordis package. The Host reads snapshots; the Client registers the `@` source. - `dsh-plugin/`: dual-face Cordis package. The Host reads snapshots; the Client registers the `@` source.
- `vscode-extension/`: VS Code companion using `window.tabGroups.all`. - `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. 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 ## 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 ```powershell
pnpm install pnpm install
@@ -29,7 +29,7 @@ Build the DSH package, then install its local profile bundle into the Web profil
```powershell ```powershell
pnpm --filter ./dsh-plugin build 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. 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> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> <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" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="all" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Prophet.CoopWithDsh\SnapshotModels.cs" Link="SnapshotModels.cs" /> <Compile Include="..\Prophet.CoopWithDsh\SnapshotModels.cs" Link="SnapshotModels.cs" />
<Compile Include="..\Prophet.CoopWithDsh\SnapshotPublisher.cs" Link="SnapshotPublisher.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,5 @@
using System; using System;
using System.IO;
using Xunit; using Xunit;
namespace Prophet.CoopWithDsh.Tests namespace Prophet.CoopWithDsh.Tests
@@ -19,5 +20,32 @@ namespace Prophet.CoopWithDsh.Tests
Assert.True(snapshot.Documents[0].Dirty); Assert.True(snapshot.Documents[0].Dirty);
Assert.Equal("2026-01-01T00:00:00.0000000Z", snapshot.UpdatedAt); 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> <AssemblyName>Prophet.CoopWithDsh</AssemblyName>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo> <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<GeneratePkgDefFile>true</GeneratePkgDefFile> <GeneratePkgDefFile>true</GeneratePkgDefFile>
<UseCodebase>true</UseCodebase>
<CreateVsixContainer>true</CreateVsixContainer> <CreateVsixContainer>true</CreateVsixContainer>
<DeployExtension>false</DeployExtension> <DeployExtension>false</DeployExtension>
<IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer> <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer>
@@ -17,7 +18,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.31902.203" ExcludeAssets="runtime" /> <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="Microsoft.VSSDK.BuildTools" Version="17.9.3168" PrivateAssets="all" GeneratePathProperty="true" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="source.extension.vsixmanifest" /> <None Include="source.extension.vsixmanifest" />
@@ -2,26 +2,28 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Runtime.Serialization;
namespace Prophet.CoopWithDsh namespace Prophet.CoopWithDsh
{ {
[DataContract]
internal sealed class OpenDocument internal sealed class OpenDocument
{ {
[JsonProperty("path")] public string Path { get; set; } = string.Empty; [DataMember(Name = "path", Order = 1)] public string Path { get; set; } = string.Empty;
[JsonProperty("label")] public string Label { get; set; } = string.Empty; [DataMember(Name = "label", Order = 2)] public string Label { get; set; } = string.Empty;
[JsonProperty("dirty")] public bool Dirty { get; set; } [DataMember(Name = "dirty", Order = 3)] public bool Dirty { get; set; }
} }
[DataContract]
internal sealed class OpenFileSnapshot internal sealed class OpenFileSnapshot
{ {
[JsonProperty("version")] public int Version { get; set; } = 1; [DataMember(Name = "version", Order = 1)] public int Version { get; set; } = 1;
[JsonProperty("instanceId")] public string InstanceId { get; set; } = string.Empty; [DataMember(Name = "instanceId", Order = 2)] public string InstanceId { get; set; } = string.Empty;
[JsonProperty("ide")] public string Ide { get; set; } = "visualstudio"; [DataMember(Name = "ide", Order = 3)] public string Ide { get; set; } = "visualstudio";
[JsonProperty("processId")] public int ProcessId { get; set; } [DataMember(Name = "processId", Order = 4)] public int ProcessId { get; set; }
[JsonProperty("workspace", NullValueHandling = NullValueHandling.Ignore)] public string? Workspace { get; set; } [DataMember(Name = "workspace", Order = 5, EmitDefaultValue = false)] public string? Workspace { get; set; }
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; } = string.Empty; [DataMember(Name = "updatedAt", Order = 6)] public string UpdatedAt { get; set; } = string.Empty;
[JsonProperty("documents")] public IReadOnlyList<OpenDocument> Documents { get; set; } = Array.Empty<OpenDocument>(); [DataMember(Name = "documents", Order = 7)] public OpenDocument[] Documents { get; set; } = Array.Empty<OpenDocument>();
} }
internal static class SnapshotModel internal static class SnapshotModel
@@ -1,12 +1,13 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.Serialization.Json;
using System.Text; using System.Text;
using Newtonsoft.Json;
namespace Prophet.CoopWithDsh namespace Prophet.CoopWithDsh
{ {
internal sealed class SnapshotPublisher : IDisposable internal sealed class SnapshotPublisher : IDisposable
{ {
private static readonly DataContractJsonSerializer Serializer = new DataContractJsonSerializer(typeof(OpenFileSnapshot));
private readonly object gate = new object(); private readonly object gate = new object();
internal string InstanceId { get; } = Guid.NewGuid().ToString("N"); internal string InstanceId { get; } = Guid.NewGuid().ToString("N");
internal string FilePath { get; } internal string FilePath { get; }
@@ -26,7 +27,11 @@ namespace Prophet.CoopWithDsh
var directory = Path.GetDirectoryName(FilePath)!; var directory = Path.GetDirectoryName(FilePath)!;
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
var temporary = FilePath + "." + Guid.NewGuid().ToString("N") + ".tmp"; 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 try
{ {
if (File.Exists(FilePath)) File.Replace(temporary, FilePath, null); if (File.Exists(FilePath)) File.Replace(temporary, FilePath, null);
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?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"> <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> <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> <DisplayName>DSH Open File Bridge</DisplayName>
<Description xml:space="preserve">Publishes Visual Studio open file metadata for DeepSeek Harness @ references.</Description> <Description xml:space="preserve">Publishes Visual Studio open file metadata for DeepSeek Harness @ references.</Description>
<License>LICENSE.txt</License> <License>LICENSE.txt</License>
</Metadata> </Metadata>
<Installation> <Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Community" 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,18.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,18.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget> <InstallationTarget Id="Microsoft.VisualStudio.Enterprise" Version="[17.0,19.0)"><ProductArchitecture>amd64</ProductArchitecture></InstallationTarget>
</Installation> </Installation>
<Dependencies /> <Dependencies />
<Prerequisites> <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> </Prerequisites>
<Assets> <Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="Prophet.CoopWithDsh" Path="|Prophet.CoopWithDsh;PkgdefProjectOutputGroup|" /> <Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="Prophet.CoopWithDsh" Path="|Prophet.CoopWithDsh;PkgdefProjectOutputGroup|" />
+3 -3
View File
@@ -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. Only absolute, existing local files are published. Dirty state is read through `IVsPersistDocData`; source buffers are never copied.
## Build and test ## 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 ```powershell
dotnet restore CoopWithDsh.sln dotnet restore CoopWithDsh.sln