Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,18 @@ For this app:
- the repo-root lowercase `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity
- local and CI build commands must pass `-warnaserror`; warnings are not an acceptable "green" build state in this repository
- do not run parallel `dotnet` or `MSBuild` work that shares the same checkout, target outputs, or NuGet package cache; the multi-target Uno app must build serially in CI to avoid `Uno.Resizetizer` file-lock failures
- do not commit user-specific local paths, usernames, or machine-specific identifiers in tests, docs, snapshots, or fixtures; use neutral synthetic values so the repo stays portable and does not leak personal machine details
- quality gates should prefer analyzer-backed build failures over separate one-off CI tools; for overloaded methods and maintainability drift, enable build-time analyzers such as `CA1502` instead of adding a formatting-only gate
- `Directory.Build.props` owns the shared analyzer and warning policy for future projects
- `Directory.Packages.props` owns centrally managed package versions
- `global.json` pins the .NET SDK and Uno SDK version used by the app and tests
- `DotPilot/DotPilot.csproj` keeps `GenerateDocumentationFile=true` with `CS1591` suppressed so `IDE0005` stays enforceable in CI across all target frameworks without inventing command-line-only build flags
- architecture work must keep a vertical-slice shape: each feature owns its contracts, orchestration, and tests behind clear boundaries instead of growing a shared horizontal service layer
- keep the Uno app project presentation-only; domain, runtime host, orchestration, integrations, and persistence code must live in separate class-library projects so UI composition does not mix with feature implementation
- structure both `DotPilot.Tests` and `DotPilot.UITests` by vertical slice and explicit harness boundaries; do not keep test files in one flat project-root pile
- GitHub is the backlog, not the product: use issues and PRs only to drive task scope and traceability, and never copy GitHub issue text, labels, workflow language, or tracker metadata into production code, runtime snapshots, or user-facing UI
- Desktop responsiveness is a product requirement: avoid synchronous probe, filesystem, network, or process work on UI-facing construction and navigation paths so the app stays fast and immediately reactive
- Do not invent a repo-specific product framing such as "workbench" unless the active issue or feature spec explicitly uses it; implement the app features described in the backlog instead of turning internal implementation language into the product narrative
- 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
- 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
- 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
Expand Down Expand Up @@ -302,6 +307,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
- Hardcoded values are forbidden.
- String literals are forbidden in implementation code. Declare them once as named constants, enums, configuration entries, or dedicated value objects, then reuse those symbols.
- Avoid magic literals. Extract shared values into constants, enums, configuration, or dedicated types.
- Backlog metadata does not belong in product code: issue numbers, PR numbers, review language, and planning terminology must never appear in production runtime models, diagnostics, or user-facing text unless the feature explicitly exposes source-control metadata.
- Design boundaries so real behaviour can be tested through public interfaces.
- For `.NET`, the repo-root `.editorconfig` is the source of truth for formatting, naming, style, and analyzer severity.
- Use nested `.editorconfig` files when they serve a clear subtree-specific purpose. Do not let IDE defaults, pipeline flags, and repo config disagree.
Expand Down Expand Up @@ -360,6 +366,7 @@ Ask first:
- Installing stale, non-canonical, or non-`mcaf-*` skills into the repo-local agent skill directory.
- Moving root governance out of the repository root.
- Mixing multiple `.NET` test frameworks in the active solution without a documented migration plan.
- Running build, test, or verification commands for file-only structural reorganizations when the user explicitly asked for folder cleanup without behavior changes.
- Adding fallback paths or alternate harnesses that only make failures disappear in tests while the primary product path remains broken.
- Switching desktop Uno pages into stacked or mobile-style responsive layouts during resize work unless the user explicitly asks for a different composition; desktop pages must stay desktop-first and protect geometry through sizing constraints instead.
- Adding extra UI-test orchestration complexity when the actual goal is simply to run the tests and get an honest pass or fail result.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace DotPilot.Core.Features.ToolchainCenter;

public sealed record ToolchainCenterWorkstreamDescriptor(
int IssueNumber,
string IssueLabel,
string SectionLabel,
string Name,
string Summary);

Expand Down Expand Up @@ -37,7 +37,7 @@ public sealed record ToolchainPollingDescriptor(

public sealed record ToolchainProviderSnapshot(
int IssueNumber,
string IssueLabel,
string SectionLabel,
ProviderDescriptor Provider,
string ExecutablePath,
string InstalledVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace DotPilot.Runtime.Features.RuntimeFoundation;
public sealed class DeterministicAgentRuntimeClient : IAgentRuntimeClient
{
private const string ApprovalKeyword = "approval";
private const string DeterministicProviderDisplayName = "Deterministic Runtime Client";
private const string PlanSummary =
"Planned the runtime foundation flow with contracts first, then communication, host lifecycle, and orchestration.";
private const string ExecuteSummary =
Expand All @@ -35,7 +34,7 @@ public ValueTask<Result<AgentTurnResult>> ExecuteAsync(AgentTurnRequest request,
Result<AgentTurnResult>.Fail(
RuntimeCommunicationProblems.ProviderUnavailable(
request.ProviderStatus,
DeterministicProviderDisplayName)));
ProviderToolchainNames.DeterministicClientDisplayName)));
}

return ValueTask.FromResult(request.Mode switch
Expand Down Expand Up @@ -77,12 +76,12 @@ private static ArtifactDescriptor CreateArtifact(SessionId sessionId, string art
{
return new ArtifactDescriptor
{
Id = ArtifactId.New(),
Id = RuntimeFoundationDeterministicIdentity.CreateArtifactId(sessionId, artifactName),
SessionId = sessionId,
Name = artifactName,
Kind = artifactKind,
RelativePath = artifactName,
CreatedAt = DateTimeOffset.UtcNow,
CreatedAt = RuntimeFoundationDeterministicIdentity.ArtifactCreatedAt,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ status is ProviderConnectionStatus.Available

return new ProviderDescriptor
{
Id = ProviderId.New(),
Id = RuntimeFoundationDeterministicIdentity.CreateProviderId(commandName),
DisplayName = displayName,
CommandName = commandName,
Status = status,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
using DotPilot.Core.Features.ControlPlaneDomain;
using DotPilot.Core.Features.RuntimeFoundation;
using DotPilot.Runtime.Features.ToolchainCenter;

namespace DotPilot.Runtime.Features.RuntimeFoundation;

public sealed class RuntimeFoundationCatalog : IRuntimeFoundationCatalog
{
private const string EpicSummary =
"Issue #12 is staged into isolated contracts, communication, host, and orchestration slices so the Uno workbench can stay presentation-only.";
"Runtime contracts, host sequencing, and orchestration seams stay isolated so the Uno app can remain presentation-only.";
private const string EpicLabelValue = "LOCAL RUNTIME READINESS";
private const string DeterministicProbePrompt =
"Summarize the runtime foundation readiness for a local-first session that may require approval.";
private const string DeterministicClientStatusSummary = "Always available for in-repo and CI validation.";
private const string DomainModelLabel = "DOMAIN";
private const string DomainModelName = "Domain contracts";
private const string DomainModelSummary =
"Typed identifiers and durable agent, session, fleet, provider, and runtime contracts live outside the Uno app.";
private const string CommunicationLabel = "CONTRACTS";
private const string CommunicationName = "Communication contracts";
private const string CommunicationSummary =
"Public result and problem boundaries are isolated so later provider and orchestration slices share one contract language.";
private const string HostLabel = "HOST";
private const string HostName = "Embedded host";
private const string HostSummary =
"The Orleans host integration point is sequenced behind dedicated runtime contracts instead of being baked into page code.";
private const string OrchestrationLabel = "ORCHESTRATION";
private const string OrchestrationName = "Orchestration runtime";
private const string OrchestrationSummary =
"Agent Framework integration is prepared as a separate slice that can plug into the embedded host without reshaping the UI layer.";
private readonly IReadOnlyList<ProviderDescriptor> _providers;

public RuntimeFoundationCatalog() => _providers = Array.AsReadOnly(CreateProviders());

public RuntimeFoundationSnapshot GetSnapshot()
{
return new(
RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedAgentRuntimeHostEpic),
EpicLabelValue,
EpicSummary,
ProviderToolchainNames.DeterministicClientDisplayName,
DeterministicProbePrompt,
CreateSlices(),
CreateProviders());
_providers);
}

private static IReadOnlyList<RuntimeSliceDescriptor> CreateSlices()
Expand All @@ -41,46 +47,44 @@ private static IReadOnlyList<RuntimeSliceDescriptor> CreateSlices()
[
new(
RuntimeFoundationIssues.DomainModel,
RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.DomainModel),
DomainModelLabel,
DomainModelName,
DomainModelSummary,
RuntimeSliceState.ReadyForImplementation),
new(
RuntimeFoundationIssues.CommunicationContracts,
RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.CommunicationContracts),
CommunicationLabel,
CommunicationName,
CommunicationSummary,
RuntimeSliceState.Sequenced),
new(
RuntimeFoundationIssues.EmbeddedOrleansHost,
RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.EmbeddedOrleansHost),
HostLabel,
HostName,
HostSummary,
RuntimeSliceState.Sequenced),
new(
RuntimeFoundationIssues.AgentFrameworkRuntime,
RuntimeFoundationIssues.FormatIssueLabel(RuntimeFoundationIssues.AgentFrameworkRuntime),
OrchestrationLabel,
OrchestrationName,
OrchestrationSummary,
RuntimeSliceState.Sequenced),
];
}

private static IReadOnlyList<ProviderDescriptor> CreateProviders()
private static ProviderDescriptor[] CreateProviders()
{
return
[
new ProviderDescriptor
{
Id = ProviderId.New(),
Id = RuntimeFoundationDeterministicIdentity.CreateProviderId(ProviderToolchainNames.DeterministicClientCommandName),
DisplayName = ProviderToolchainNames.DeterministicClientDisplayName,
CommandName = ProviderToolchainNames.DeterministicClientCommandName,
Status = ProviderConnectionStatus.Available,
StatusSummary = DeterministicClientStatusSummary,
RequiresExternalToolchain = false,
},
.. ToolchainProviderSnapshotFactory.Create(TimeProvider.System.GetUtcNow())
.Select(snapshot => snapshot.Provider),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Security.Cryptography;
using System.Text;
using DotPilot.Core.Features.ControlPlaneDomain;

namespace DotPilot.Runtime.Features.RuntimeFoundation;

internal static class RuntimeFoundationDeterministicIdentity
{
private const string ArtifactSeedPrefix = "runtime-foundation-artifact";
private const string ProviderSeedPrefix = "runtime-foundation-provider";
private const string SeedSeparator = "|";

public static DateTimeOffset ArtifactCreatedAt { get; } = new(2026, 3, 13, 0, 0, 0, TimeSpan.Zero);

public static ArtifactId CreateArtifactId(SessionId sessionId, string artifactName)
{
return new(CreateGuid(string.Concat(ArtifactSeedPrefix, SeedSeparator, sessionId, SeedSeparator, artifactName)));
}

public static ProviderId CreateProviderId(string commandName)
{
return new(CreateGuid(string.Concat(ProviderSeedPrefix, SeedSeparator, commandName)));
}

private static Guid CreateGuid(string seed)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(seed));
Span<byte> guidBytes = stackalloc byte[16];
hash[..guidBytes.Length].CopyTo(guidBytes);
guidBytes[7] = (byte)((guidBytes[7] & 0x0F) | 0x80);
guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);
return new Guid(guidBytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@ namespace DotPilot.Runtime.Features.ToolchainCenter;

public sealed class ToolchainCenterCatalog : IToolchainCenterCatalog, IDisposable
{
private const string EpicLabelValue = "PRE-SESSION READINESS";
private const string EpicSummary =
"Issue #14 keeps provider installation, auth, diagnostics, configuration, and polling visible before the first live session.";
"Provider installation, launch checks, authentication, configuration, and refresh state stay visible before the first live session.";
private const string UiWorkstreamLabel = "SURFACE";
private const string UiWorkstreamName = "Toolchain Center UI";
private const string UiWorkstreamSummary =
"The settings shell exposes a first-class desktop Toolchain Center with provider cards, detail panes, and operator actions.";
private const string DiagnosticsWorkstreamLabel = "DIAGNOSTICS";
private const string DiagnosticsWorkstreamName = "Connection diagnostics";
private const string DiagnosticsWorkstreamSummary =
"Launch, connection, resume, tool access, and auth diagnostics stay attributable before live work starts.";
private const string ConfigurationWorkstreamLabel = "CONFIGURATION";
private const string ConfigurationWorkstreamName = "Secrets and environment";
private const string ConfigurationWorkstreamSummary =
"Provider secrets, local overrides, and non-secret environment configuration stay visible without leaking values.";
private const string PollingWorkstreamLabel = "POLLING";
private const string PollingWorkstreamName = "Background polling";
private const string PollingWorkstreamSummary =
"Version and auth readiness refresh in the background so the workbench can surface stale state early.";
"Version and auth readiness refresh in the background so the app can surface stale state early.";
private readonly TimeProvider _timeProvider;
private readonly CancellationTokenSource _disposeTokenSource = new();
private readonly PeriodicTimer? _pollingTimer;
private readonly Task _pollingTask;
private ToolchainCenterSnapshot _snapshot;
private int _disposeState;

public ToolchainCenterCatalog()
: this(TimeProvider.System, startBackgroundPolling: true)
Expand All @@ -46,13 +52,29 @@ public ToolchainCenterCatalog(TimeProvider timeProvider, bool startBackgroundPol
}
}

public ToolchainCenterSnapshot GetSnapshot() => _snapshot;
public ToolchainCenterSnapshot GetSnapshot() => Volatile.Read(ref _snapshot);

public void Dispose()
{
if (Interlocked.Exchange(ref _disposeState, 1) != 0)
{
return;
}

_disposeTokenSource.Cancel();
_pollingTimer?.Dispose();

try
{
_pollingTask.GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
// Expected during shutdown.
}

_disposeTokenSource.Dispose();
GC.SuppressFinalize(this);
}

private async Task PollAsync()
Expand All @@ -66,21 +88,25 @@ private async Task PollAsync()
{
while (await _pollingTimer.WaitForNextTickAsync(_disposeTokenSource.Token))
{
_snapshot = EvaluateSnapshot();
Volatile.Write(ref _snapshot, EvaluateSnapshot());
}
}
catch (OperationCanceledException)
{
// Expected during app shutdown.
}
catch (ObjectDisposedException) when (_disposeTokenSource.IsCancellationRequested)
{
// Expected when the timer is disposed during shutdown.
}
}

private ToolchainCenterSnapshot EvaluateSnapshot()
{
var evaluatedAt = _timeProvider.GetUtcNow();
var providers = ToolchainProviderSnapshotFactory.Create(evaluatedAt);
return new(
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterEpic),
EpicLabelValue,
EpicSummary,
CreateWorkstreams(),
providers,
Expand All @@ -95,22 +121,22 @@ private static IReadOnlyList<ToolchainCenterWorkstreamDescriptor> CreateWorkstre
[
new(
ToolchainCenterIssues.ToolchainCenterUi,
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ToolchainCenterUi),
UiWorkstreamLabel,
UiWorkstreamName,
UiWorkstreamSummary),
new(
ToolchainCenterIssues.ConnectionDiagnostics,
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ConnectionDiagnostics),
DiagnosticsWorkstreamLabel,
DiagnosticsWorkstreamName,
DiagnosticsWorkstreamSummary),
new(
ToolchainCenterIssues.ProviderConfiguration,
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.ProviderConfiguration),
ConfigurationWorkstreamLabel,
ConfigurationWorkstreamName,
ConfigurationWorkstreamSummary),
new(
ToolchainCenterIssues.BackgroundPolling,
ToolchainCenterIssues.FormatIssueLabel(ToolchainCenterIssues.BackgroundPolling),
PollingWorkstreamLabel,
PollingWorkstreamName,
PollingWorkstreamSummary),
];
Expand Down
Loading