Skip to content

[Windows] REST + WebSocket client and data models #140

@2witstudios

Description

@2witstudios

Overview

Implement C# models mirroring the ppg manifest JSON, plus HTTP REST and WebSocket clients for ppg serve.

Data Models (Models/)

Manifest.cs

public record Manifest(
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("projectRoot")] string ProjectRoot,
    [property: JsonPropertyName("sessionName")] string SessionName,
    [property: JsonPropertyName("worktrees")] Dictionary<string, WorktreeEntry> Worktrees,
    [property: JsonPropertyName("createdAt")] string CreatedAt,
    [property: JsonPropertyName("updatedAt")] string UpdatedAt
);

public record WorktreeEntry(
    string Id, string Name, string Path, string Branch,
    [property: JsonPropertyName("baseBranch")] string BaseBranch,
    WorktreeStatus Status,
    [property: JsonPropertyName("tmuxWindow")] string TmuxWindow,
    [property: JsonPropertyName("prUrl")] string? PrUrl,
    Dictionary<string, AgentEntry> Agents,
    [property: JsonPropertyName("createdAt")] string CreatedAt,
    [property: JsonPropertyName("mergedAt")] string? MergedAt
);
// ... AgentEntry, AgentStatus, WorktreeStatus enums

Handle status aliases in JSON deserialization: idleRunning, exitedCompleted, goneLost.

AgentVariant.cs

Match macOS AgentVariant.swift: Claude, Codex, OpenCode, Terminal, Worktree with display names, icons, and prompt delivery methods.

REST Client (Services/PpgApiClient.cs)

HttpClient with Bearer token auth. Same 13+ endpoints as Linux issue #132.

public class PpgApiClient
{
    private readonly HttpClient _http;
    
    public async Task<Manifest> GetStatusAsync() =>
        await GetAsync<Manifest>("/api/status");
    
    public async Task SpawnAsync(SpawnRequest request) =>
        await PostAsync("/api/spawn", request);
    
    // ... all 13 endpoints
}

WebSocket Client (Services/WebSocketClient.cs)

System.Net.WebSockets.ClientWebSocket with:

  • Token auth via query param (/ws?token=...)
  • Exponential backoff reconnect (1s base, 30s max)
  • 30s ping keepalive via Task.Delay
  • Event dispatch to UI thread via Dispatcher.InvokeAsync
  • Same events as Linux issue [Linux] REST + WebSocket client #132
public class WebSocketClient
{
    public event Action<Manifest>? ManifestUpdated;
    public event Action<string, AgentStatus>? AgentStatusChanged;
    public event Action<string, string>? TerminalOutput;  // agentId, data
    
    public async Task ConnectAsync(string url, string token, CancellationToken ct);
    public async Task SubscribeTerminalAsync(string agentId);
    public async Task UnsubscribeTerminalAsync(string agentId);
    public async Task SendTerminalInputAsync(string agentId, string data);
}

ManifestStore (Services/ManifestStore.cs)

Observable singleton that holds the current manifest and raises PropertyChanged on updates:

public partial class ManifestStore : ObservableObject
{
    [ObservableProperty] private Manifest? _manifest;
    [ObservableProperty] private ConnectionState _state;
    
    // Updated by WebSocketClient events or REST polling
}

Token Storage (Services/TokenStorage.cs)

Use System.Security.Cryptography.ProtectedData (DPAPI) to encrypt bearer tokens at rest.
Store in %APPDATA%/PPG Desktop/credentials.dat.

References

  • Server API: src/server/index.ts, src/server/routes/, src/server/ws/
  • Manifest types: src/types/manifest.ts

Acceptance Criteria

  • All manifest types deserialize correctly from ppg serve JSON
  • REST client connects and fetches status
  • WebSocket connects, reconnects, dispatches events to UI thread
  • Terminal subscribe/output streaming works
  • Token stored securely with DPAPI
  • Unit tests for JSON deserialization

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions