Skip to content

Commit 0fcab7f

Browse files
committed
feat: add toolchain center slice
1 parent e5d6b00 commit 0fcab7f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2524
-212
lines changed

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ For this app:
138138
- `format` uses `dotnet format --verify-no-changes` as a local pre-push check; GitHub Actions validation should not spend CI time rechecking formatting drift that must already be fixed before push
139139
- coverage uses the `coverlet.collector` integration on `DotPilot.Tests` with the repo runsettings file to keep generated Uno artifacts out of the coverage path
140140
- desktop release publishing uses `dotnet publish DotPilot/DotPilot.csproj -c Release -f net10.0-desktop`; the validation workflow stays focused on build and automated tests, while the release workflow owns desktop publish outputs for macOS, Windows, and Linux
141-
- `LangVersion` is pinned to `latest` at the root
141+
- `LangVersion` is pinned to `14` at the root
142142
- prefer the newest stable `.NET 10` and `C#` language features that are supported by the pinned SDK and do not weaken readability, determinism, or analyzability
143143
- the repo-root lowercase `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity
144144
- local and CI build commands must pass `-warnaserror`; warnings are not an acceptable "green" build state in this repository
@@ -153,6 +153,7 @@ For this app:
153153
- GitHub Actions workflows must use descriptive names and filenames that reflect their purpose; do not use a generic `ci.yml` catch-all because build validation and release automation are separate operator flows
154154
- GitHub Actions must be split into at least one validation workflow for normal builds/tests and one release workflow for CI-driven version resolution, release-note generation, desktop publishing, and GitHub Release publication
155155
- meaningful GitHub review comments must be evaluated and fixed when they still apply even if the original PR was closed; closed review threads are not a reason to ignore valid engineering feedback
156+
- PR bodies for issue-backed work must use GitHub closing references such as `Closes #14` so merged work closes the tracked issue automatically
156157
- the release workflow must run automatically on pushes to `main`, build desktop apps, and publish the GitHub Release without requiring a manual dispatch
157158
- desktop app build or publish jobs must use native runners for their target OS: macOS artifacts on macOS runners, Windows artifacts on Windows runners, and Linux artifacts on Linux runners
158159
- desktop release assets must be native installable or directly executable outputs for each OS, not archives of raw publish folders; package the real `.exe`, `.snap`, `.dmg`, `.pkg`, `Setup.exe`, or equivalent runnable installer/app artifact instead of zipping intermediate publish directories
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DotPilot.Core.Features.ToolchainCenter;
2+
3+
public interface IToolchainCenterCatalog
4+
{
5+
ToolchainCenterSnapshot GetSnapshot();
6+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using DotPilot.Core.Features.ControlPlaneDomain;
2+
3+
namespace DotPilot.Core.Features.ToolchainCenter;
4+
5+
public sealed record ToolchainCenterWorkstreamDescriptor(
6+
int IssueNumber,
7+
string IssueLabel,
8+
string Name,
9+
string Summary);
10+
11+
public sealed record ToolchainActionDescriptor(
12+
string Title,
13+
string Summary,
14+
ToolchainActionKind Kind,
15+
bool IsPrimary,
16+
bool IsEnabled);
17+
18+
public sealed record ToolchainDiagnosticDescriptor(
19+
string Name,
20+
ToolchainDiagnosticStatus Status,
21+
string Summary);
22+
23+
public sealed record ToolchainConfigurationEntry(
24+
string Name,
25+
string ValueDisplay,
26+
string Summary,
27+
ToolchainConfigurationKind Kind,
28+
ToolchainConfigurationStatus Status,
29+
bool IsSensitive);
30+
31+
public sealed record ToolchainPollingDescriptor(
32+
TimeSpan RefreshInterval,
33+
DateTimeOffset LastRefreshAt,
34+
DateTimeOffset NextRefreshAt,
35+
ToolchainPollingStatus Status,
36+
string Summary);
37+
38+
public sealed record ToolchainProviderSnapshot(
39+
int IssueNumber,
40+
string IssueLabel,
41+
ProviderDescriptor Provider,
42+
string ExecutablePath,
43+
string InstalledVersion,
44+
ToolchainReadinessState ReadinessState,
45+
string ReadinessSummary,
46+
ToolchainVersionStatus VersionStatus,
47+
string VersionSummary,
48+
ToolchainAuthStatus AuthStatus,
49+
string AuthSummary,
50+
ToolchainHealthStatus HealthStatus,
51+
string HealthSummary,
52+
IReadOnlyList<ToolchainActionDescriptor> Actions,
53+
IReadOnlyList<ToolchainDiagnosticDescriptor> Diagnostics,
54+
IReadOnlyList<ToolchainConfigurationEntry> Configuration,
55+
ToolchainPollingDescriptor Polling);
56+
57+
public sealed record ToolchainCenterSnapshot(
58+
string EpicLabel,
59+
string Summary,
60+
IReadOnlyList<ToolchainCenterWorkstreamDescriptor> Workstreams,
61+
IReadOnlyList<ToolchainProviderSnapshot> Providers,
62+
ToolchainPollingDescriptor BackgroundPolling,
63+
int ReadyProviderCount,
64+
int AttentionRequiredProviderCount);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace DotPilot.Core.Features.ToolchainCenter;
2+
3+
public static class ToolchainCenterIssues
4+
{
5+
public const int ToolchainCenterEpic = 14;
6+
public const int ToolchainCenterUi = 33;
7+
public const int CodexReadiness = 34;
8+
public const int ClaudeCodeReadiness = 35;
9+
public const int GitHubCopilotReadiness = 36;
10+
public const int ConnectionDiagnostics = 37;
11+
public const int ProviderConfiguration = 38;
12+
public const int BackgroundPolling = 39;
13+
14+
private const string IssueLabelFormat = "ISSUE #{0}";
15+
private static readonly System.Text.CompositeFormat IssueLabelCompositeFormat =
16+
System.Text.CompositeFormat.Parse(IssueLabelFormat);
17+
18+
public static string FormatIssueLabel(int issueNumber) =>
19+
string.Format(System.Globalization.CultureInfo.InvariantCulture, IssueLabelCompositeFormat, issueNumber);
20+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
namespace DotPilot.Core.Features.ToolchainCenter;
2+
3+
public enum ToolchainReadinessState
4+
{
5+
Missing,
6+
ActionRequired,
7+
Limited,
8+
Ready,
9+
}
10+
11+
public enum ToolchainVersionStatus
12+
{
13+
Missing,
14+
Unknown,
15+
Detected,
16+
UpdateAvailable,
17+
}
18+
19+
public enum ToolchainAuthStatus
20+
{
21+
Missing,
22+
Unknown,
23+
Connected,
24+
}
25+
26+
public enum ToolchainHealthStatus
27+
{
28+
Blocked,
29+
Warning,
30+
Healthy,
31+
}
32+
33+
public enum ToolchainDiagnosticStatus
34+
{
35+
Blocked,
36+
Failed,
37+
Warning,
38+
Ready,
39+
Passed,
40+
}
41+
42+
public enum ToolchainConfigurationKind
43+
{
44+
Secret,
45+
EnvironmentVariable,
46+
Setting,
47+
}
48+
49+
public enum ToolchainConfigurationStatus
50+
{
51+
Missing,
52+
Partial,
53+
Configured,
54+
}
55+
56+
public enum ToolchainActionKind
57+
{
58+
Install,
59+
Connect,
60+
Update,
61+
TestConnection,
62+
Troubleshoot,
63+
OpenDocs,
64+
}
65+
66+
public enum ToolchainPollingStatus
67+
{
68+
Idle,
69+
Healthy,
70+
Warning,
71+
}

DotPilot.Core/Features/Workbench/WorkbenchSettingsContracts.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
namespace DotPilot.Core.Features.Workbench;
22

3+
public static class WorkbenchSettingsCategoryKeys
4+
{
5+
public const string Toolchains = "toolchains";
6+
public const string Providers = "providers";
7+
public const string Policies = "policies";
8+
public const string Storage = "storage";
9+
}
10+
311
public sealed record WorkbenchSettingEntry(
412
string Name,
513
string Value,

DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainNames.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ internal static class ProviderToolchainNames
88
public const string CodexCommandName = "codex";
99
public const string ClaudeCodeDisplayName = "Claude Code";
1010
public const string ClaudeCodeCommandName = "claude";
11-
public const string GitHubCopilotDisplayName = "GitHub CLI / Copilot";
11+
public const string GitHubCopilotDisplayName = "GitHub Copilot";
1212
public const string GitHubCopilotCommandName = "gh";
1313
}

DotPilot.Runtime/Features/RuntimeFoundation/ProviderToolchainProbe.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ status is ProviderConnectionStatus.Available
3535
};
3636
}
3737

38-
private static string? ResolveExecutablePath(string commandName)
38+
internal static string? ResolveExecutablePath(string commandName)
3939
{
4040
if (OperatingSystem.IsBrowser())
4141
{

DotPilot.Runtime/Features/RuntimeFoundation/RuntimeFoundationCatalog.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DotPilot.Core.Features.ControlPlaneDomain;
22
using DotPilot.Core.Features.RuntimeFoundation;
3+
using DotPilot.Runtime.Features.ToolchainCenter;
34

45
namespace DotPilot.Runtime.Features.RuntimeFoundation;
56

@@ -78,18 +79,8 @@ private static IReadOnlyList<ProviderDescriptor> CreateProviders()
7879
StatusSummary = DeterministicClientStatusSummary,
7980
RequiresExternalToolchain = false,
8081
},
81-
ProviderToolchainProbe.Probe(
82-
ProviderToolchainNames.CodexDisplayName,
83-
ProviderToolchainNames.CodexCommandName,
84-
true),
85-
ProviderToolchainProbe.Probe(
86-
ProviderToolchainNames.ClaudeCodeDisplayName,
87-
ProviderToolchainNames.ClaudeCodeCommandName,
88-
true),
89-
ProviderToolchainProbe.Probe(
90-
ProviderToolchainNames.GitHubCopilotDisplayName,
91-
ProviderToolchainNames.GitHubCopilotCommandName,
92-
true),
82+
.. ToolchainProviderSnapshotFactory.Create(TimeProvider.System.GetUtcNow())
83+
.Select(snapshot => snapshot.Provider),
9384
];
9485
}
9586
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using DotPilot.Core.Features.ToolchainCenter;
2+
3+
namespace DotPilot.Runtime.Features.ToolchainCenter;
4+
5+
public sealed class ToolchainCenterCatalog : IToolchainCenterCatalog, IDisposable
6+
{
7+
private const string EpicSummary =
8+
"Issue #14 keeps provider installation, auth, diagnostics, configuration, and polling visible before the first live session.";
9+
private const string UiWorkstreamName = "Toolchain Center UI";
10+
private const string UiWorkstreamSummary =
11+
"The settings shell exposes a first-class desktop Toolchain Center with provider cards, detail panes, and operator actions.";
12+
private const string DiagnosticsWorkstreamName = "Connection diagnostics";
13+
private const string DiagnosticsWorkstreamSummary =
14+
"Launch, connection, resume, tool access, and auth diagnostics stay attributable before live work starts.";
15+
private const string ConfigurationWorkstreamName = "Secrets and environment";
16+
private const string ConfigurationWorkstreamSummary =
17+
"Provider secrets, local overrides, and non-secret environment configuration stay visible without leaking values.";
18+
private const string PollingWorkstreamName = "Background polling";
19+
private const string PollingWorkstreamSummary =
20+
"Version and auth readiness refresh in the background so the workbench can surface stale state early.";
21+
private readonly TimeProvider _timeProvider;
22+
private readonly CancellationTokenSource _disposeTokenSource = new();
23+
private readonly PeriodicTimer? _pollingTimer;
24+
private readonly Task _pollingTask;
25+
private ToolchainCenterSnapshot _snapshot;
26+
27+
public ToolchainCenterCatalog()
28+
: this(TimeProvider.System, startBackgroundPolling: true)
29+
{
30+
}
31+
32+
public ToolchainCenterCatalog(TimeProvider timeProvider, bool startBackgroundPolling)
33+
{
34+
ArgumentNullException.ThrowIfNull(timeProvider);
35+
36+
_timeProvider = timeProvider;
37+
_snapshot = EvaluateSnapshot();
38+
if (startBackgroundPolling)
39+
{
40+
_pollingTimer = new PeriodicTimer(TimeSpan.FromMinutes(5), timeProvider);
41+
_pollingTask = Task.Run(PollAsync);
42+
}
43+
else
44+
{
45+
_pollingTask = Task.CompletedTask;
46+
}
47+
}
48+
49+
public ToolchainCenterSnapshot GetSnapshot() => _snapshot;
50+
51+
public void Dispose()
52+
{
53+
_disposeTokenSource.Cancel();
54+
_pollingTimer?.Dispose();
55+
_disposeTokenSource.Dispose();
56+
}
57+
58+
private async Task PollAsync()
59+
{
60+
if (_pollingTimer is null)
61+
{
62+
return;
63+
}
64+
65+
try
66+
{
67+
while (await _pollingTimer.WaitForNextTickAsync(_disposeTokenSource.Token))
68+
{
69+
_snapshot = EvaluateSnapshot();
70+
}
71+
}
72+
catch (OperationCanceledException)
73+
{
74+
// Expected during app shutdown.
75+
}
76+
}
77+
78+
private ToolchainCenterSnapshot EvaluateSnapshot()
79+
{
80+
var evaluatedAt = _timeProvider.GetUtcNow();
81+
var providers = ToolchainProviderSnapshotFactory.Create(evaluatedAt);
82+
return new(
83+
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterEpic),
84+
EpicSummary,
85+
CreateWorkstreams(),
86+
providers,
87+
ToolchainProviderSnapshotFactory.CreateBackgroundPolling(providers, evaluatedAt),
88+
providers.Count(provider => provider.ReadinessState is ToolchainReadinessState.Ready),
89+
providers.Count(provider => provider.ReadinessState is not ToolchainReadinessState.Ready));
90+
}
91+
92+
private static IReadOnlyList<ToolchainCenterWorkstreamDescriptor> CreateWorkstreams()
93+
{
94+
return
95+
[
96+
new(
97+
ToolchainCenterIssues.ToolchainCenterUi,
98+
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterUi),
99+
UiWorkstreamName,
100+
UiWorkstreamSummary),
101+
new(
102+
ToolchainCenterIssues.ConnectionDiagnostics,
103+
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ConnectionDiagnostics),
104+
DiagnosticsWorkstreamName,
105+
DiagnosticsWorkstreamSummary),
106+
new(
107+
ToolchainCenterIssues.ProviderConfiguration,
108+
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ProviderConfiguration),
109+
ConfigurationWorkstreamName,
110+
ConfigurationWorkstreamSummary),
111+
new(
112+
ToolchainCenterIssues.BackgroundPolling,
113+
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.BackgroundPolling),
114+
PollingWorkstreamName,
115+
PollingWorkstreamSummary),
116+
];
117+
}
118+
}

0 commit comments

Comments
 (0)